From 82cdb657190675f1d23b5d68ceb5e110aed93fe9 Mon Sep 17 00:00:00 2001 From: Brad Kent Date: Sun, 14 Jan 2024 19:34:29 -0600 Subject: [PATCH] Focus on reducing complexity move TYPE_x constants to new Type class method params - new values : isPassedByReference & isVariadic.. name no longer contains $, &, or ... --- src/CurlHttpMessage/Client.php | 50 +- src/CurlHttpMessage/ClientAsync.php | 7 +- src/CurlHttpMessage/CurlReqResOptions.php | 6 +- src/CurlHttpMessage/Handler/Mock.php | 69 ++- .../Middleware/FollowLocation.php | 188 ++++---- src/Debug/AbstractDebug.php | 7 +- src/Debug/Abstraction/AbstractArray.php | 9 +- src/Debug/Abstraction/AbstractObject.php | 21 +- src/Debug/Abstraction/AbstractString.php | 59 ++- src/Debug/Abstraction/Abstracter.php | 247 +--------- src/Debug/Abstraction/Abstraction.php | 7 +- ...nheritable.php => AbstractInheritable.php} | 4 +- src/Debug/Abstraction/Object/Abstraction.php | 7 +- src/Debug/Abstraction/Object/Constants.php | 4 +- src/Debug/Abstraction/Object/Definition.php | 4 +- src/Debug/Abstraction/Object/Helper.php | 66 ++- src/Debug/Abstraction/Object/MethodParams.php | 101 ++-- src/Debug/Abstraction/Object/Methods.php | 164 +++---- src/Debug/Abstraction/Object/Properties.php | 58 ++- .../Abstraction/Object/PropertiesDom.php | 5 +- src/Debug/Abstraction/Object/Subscriber.php | 9 +- src/Debug/Abstraction/Type.php | 243 ++++++++++ .../Collector/AbstractAsyncMiddleware.php | 9 +- .../Collector/MonologHandlerCompatTrait.php | 5 +- .../MonologHandlerCompatTrait_2.0.php | 2 + .../Collector/MySqli/ExecuteQueryTrait.php | 7 +- .../MySqli/ExecuteQueryTrait_php8.2.php | 13 +- .../Pdo/MethodSignatureCompatTrait.php | 10 +- .../Pdo/MethodSignatureCompatTrait_php5.6.php | 3 + src/Debug/Collector/PhpCurlClass.php | 24 +- src/Debug/Collector/SoapClient.php | 6 +- src/Debug/Collector/StatementInfo.php | 6 +- src/Debug/Collector/SwiftMailerLogger.php | 4 +- src/Debug/Config.php | 20 +- src/Debug/Debug.php | 10 +- src/Debug/Dump/Base.php | 9 +- src/Debug/Dump/BaseValue.php | 34 +- src/Debug/Dump/Html.php | 115 +---- src/Debug/Dump/Html/AbstractObjectSection.php | 129 +++--- src/Debug/Dump/Html/Group.php | 143 ++++++ src/Debug/Dump/Html/Helper.php | 13 +- src/Debug/Dump/Html/HtmlObject.php | 90 +--- src/Debug/Dump/Html/HtmlString.php | 16 +- src/Debug/Dump/Html/HtmlStringEncoded.php | 12 +- src/Debug/Dump/Html/ObjectMethods.php | 10 +- src/Debug/Dump/Html/ObjectPhpDoc.php | 113 +++++ src/Debug/Dump/Html/Value.php | 71 ++- src/Debug/Dump/Substitution.php | 15 +- src/Debug/Dump/Text.php | 21 +- src/Debug/Dump/TextAnsiValue.php | 47 +- src/Debug/Dump/TextValue.php | 65 ++- src/Debug/Framework/Cake4.php | 8 +- src/Debug/Framework/Laravel/Middleware.php | 6 +- .../Framework/Laravel/ServiceProvider.php | 10 +- src/Debug/Framework/Yii1_1/Component.php | 10 +- src/Debug/Framework/Yii2/LogUser.php | 4 +- src/Debug/Framework/Yii2/Module.php | 10 +- src/Debug/Plugin/AbstractLogReqRes.php | 25 +- src/Debug/Plugin/Channel.php | 6 +- src/Debug/Plugin/ConfigEvents.php | 7 +- src/Debug/Plugin/Highlight.php | 6 +- src/Debug/Plugin/InternalEvents.php | 42 +- src/Debug/Plugin/LogFiles.php | 4 +- src/Debug/Plugin/LogPhp.php | 22 +- src/Debug/Plugin/LogRequest.php | 10 +- src/Debug/Plugin/Method/Basic.php | 14 +- src/Debug/Plugin/Method/Clear.php | 4 +- src/Debug/Plugin/Method/Group.php | 6 +- src/Debug/Plugin/Method/Profile.php | 81 ++-- src/Debug/Plugin/Method/ReqRes.php | 4 +- src/Debug/Plugin/Method/Table.php | 26 +- src/Debug/Plugin/Method/Time.php | 4 +- src/Debug/Plugin/Redaction.php | 12 +- src/Debug/Psr3/MethodSignatureCompatTrait.php | 8 +- .../Psr3/MethodSignatureCompatTrait_2.php | 2 + .../Psr3/MethodSignatureCompatTrait_3.php | 2 + src/Debug/Route/AbstractRoute.php | 15 +- src/Debug/Route/ChromeLogger.php | 13 +- src/Debug/Route/Discord.php | 8 +- src/Debug/Route/Firephp.php | 9 +- src/Debug/Route/Html/Tabs.php | 27 +- src/Debug/Route/Script.php | 98 ++-- src/Debug/Route/Slack.php | 7 +- src/Debug/Route/Teams.php | 7 +- src/Debug/Route/Wamp.php | 97 ++-- src/Debug/Route/WampCrate.php | 14 +- src/Debug/ServiceProvider.php | 6 +- src/Debug/Utility/ArrayUtil.php | 77 ++-- src/Debug/Utility/FileStreamWrapper.php | 246 +--------- src/Debug/Utility/FileStreamWrapperBase.php | 262 +++++++++++ src/Debug/Utility/FileTree.php | 8 +- src/Debug/Utility/Php.php | 4 +- src/Debug/Utility/PhpDoc.php | 430 ++++++------------ src/Debug/Utility/PhpDoc/Helper.php | 108 +++++ src/Debug/Utility/PhpDoc/ParseMethod.php | 117 +++++ src/Debug/Utility/PhpDoc/ParseParam.php | 84 ++++ src/Debug/Utility/PhpDoc/Parsers.php | 134 ++++++ src/Debug/Utility/PhpDoc/Type.php | 130 ++++++ src/Debug/Utility/PhpDocBase.php | 228 ---------- src/Debug/Utility/Profile.php | 6 +- src/Debug/Utility/SerializeLog.php | 5 +- src/Debug/Utility/StringUtil.php | 8 +- src/Debug/Utility/Table.php | 6 +- src/Debug/Utility/TableRow.php | 5 +- src/Debug/Utility/UseStatements.php | 2 +- src/Debug/Utility/Utf8Buffer.php | 4 +- src/Debug/Utility/Utility.php | 3 +- src/ErrorHandler/AbstractComponent.php | 4 +- src/ErrorHandler/AbstractError.php | 343 ++++++++++++++ src/ErrorHandler/AbstractErrorHandler.php | 85 +++- src/ErrorHandler/Error.php | 344 ++------------ src/ErrorHandler/ErrorHandler.php | 80 +--- src/ErrorHandler/Plugin/Stats.php | 6 +- src/ErrorHandler/Plugin/StatsStoreFile.php | 4 +- src/HttpMessage/AbstractServerRequest.php | 3 +- src/HttpMessage/UploadedFile.php | 9 +- src/HttpMessage/Utility/Uri.php | 35 +- src/Promise/EachPromise.php | 14 +- src/PubSub/Event.php | 10 +- src/PubSub/ValueStore.php | 8 +- src/Teams/Elements/Table.php | 57 ++- src/Teams/Elements/TableCell.php | 51 +-- tests/Debug/Abstraction/AbstractionTest.php | 4 +- .../CurlHttpMessageMiddlewareTest.php | 5 +- .../Debug/Collector/GuzzleMiddlewareTest.php | 5 +- tests/Debug/Collector/OAuthTest.php | 17 +- tests/Debug/Collector/PdoTest.php | 5 +- tests/Debug/Collector/PhpCurlClassTest.php | 6 +- tests/Debug/Collector/SoapClientTest.php | 11 +- tests/Debug/Plugin/InternalEventsTest.php | 1 + tests/Debug/Plugin/LogRequestTest.php | 22 +- tests/Debug/Plugin/Method/GeneralTest.php | 4 +- tests/Debug/Plugin/Method/GroupTest.php | 4 +- tests/Debug/Plugin/Method/ProfileTest.php | 7 +- tests/Debug/Plugin/Method/TableTest.php | 4 +- tests/Debug/Plugin/PrettifyTest.php | 9 +- tests/Debug/Route/WampTest.php | 11 +- tests/Debug/SubstitutionTest.php | 9 +- tests/Debug/Type/ArrayTest.php | 3 +- tests/Debug/Type/BasicTest.php | 66 +-- tests/Debug/Type/EnumTest.php | 3 +- tests/Debug/Type/ObjectTest.php | 38 +- tests/Debug/Type/StringTest.php | 25 +- tests/Debug/Utility/FileStreamWrapperTest.php | 1 + tests/Debug/Utility/PhpDocTest.php | 38 +- tests/Debug/Utility/SerializeLogTest.php | 3 +- tests/Teams/Elements/TableCellTest.php | 4 +- tests/Teams/Elements/TableRowTest.php | 2 +- tests/Teams/Elements/TableTest.php | 4 +- tests/bootstrap.php | 17 +- tests/docroot/frontController.php | 2 +- 151 files changed, 3506 insertions(+), 2735 deletions(-) rename src/Debug/Abstraction/Object/{Inheritable.php => AbstractInheritable.php} (97%) create mode 100644 src/Debug/Abstraction/Type.php create mode 100644 src/Debug/Dump/Html/Group.php create mode 100644 src/Debug/Dump/Html/ObjectPhpDoc.php create mode 100644 src/Debug/Utility/FileStreamWrapperBase.php create mode 100644 src/Debug/Utility/PhpDoc/Helper.php create mode 100644 src/Debug/Utility/PhpDoc/ParseMethod.php create mode 100644 src/Debug/Utility/PhpDoc/ParseParam.php create mode 100644 src/Debug/Utility/PhpDoc/Parsers.php create mode 100644 src/Debug/Utility/PhpDoc/Type.php delete mode 100644 src/Debug/Utility/PhpDocBase.php create mode 100644 src/ErrorHandler/AbstractError.php diff --git a/src/CurlHttpMessage/Client.php b/src/CurlHttpMessage/Client.php index 6a765b9d..2025b5ef 100644 --- a/src/CurlHttpMessage/Client.php +++ b/src/CurlHttpMessage/Client.php @@ -84,7 +84,7 @@ public function __destruct() * * @return PromiseInterface|ResponseInterface */ - public function delete($uri, $headers = array(), $body = null) + public function delete($uri, array $headers = array(), $body = null) { return $this->request('DELETE', $uri, array( 'body' => $body, @@ -96,7 +96,7 @@ public function delete($uri, $headers = array(), $body = null) * Send a GET request * * @param UriInterface|string $uri UriInterface or string - * @param array $headers Request headers + * @param array|null $headers Request headers * * @return PromiseInterface|ResponseInterface */ @@ -115,7 +115,7 @@ public function get($uri, $headers = array()) * * @return PromiseInterface|ResponseInterface */ - public function head($uri, $headers = array()) + public function head($uri, array $headers = array()) { return $this->request('HEAD', $uri, array( 'headers' => $headers, @@ -131,7 +131,7 @@ public function head($uri, $headers = array()) * * @return PromiseInterface|ResponseInterface */ - public function options($uri, $headers = array(), $body = null) + public function options($uri, array $headers = array(), $body = null) { return $this->request('OPTIONS', $uri, array( 'body' => $body, @@ -148,7 +148,7 @@ public function options($uri, $headers = array(), $body = null) * * @return PromiseInterface|ResponseInterface */ - public function patch($uri, $headers = array(), $body = null) + public function patch($uri, array $headers = array(), $body = null) { return $this->request('PATCH', $uri, array( 'body' => $body, @@ -165,7 +165,7 @@ public function patch($uri, $headers = array(), $body = null) * * @return PromiseInterface|ResponseInterface */ - public function post($uri, $headers = array(), $body = null) + public function post($uri, array $headers = array(), $body = null) { return $this->request('POST', $uri, array( 'body' => $body, @@ -182,7 +182,7 @@ public function post($uri, $headers = array(), $body = null) * * @return PromiseInterface|ResponseInterface */ - public function put($uri, $headers = array(), $body = null) + public function put($uri, array $headers = array(), $body = null) { return $this->request('PUT', $uri, array( 'body' => $body, @@ -198,7 +198,7 @@ public function put($uri, $headers = array(), $body = null) * * @return PromiseInterface|ResponseInterface */ - public function trace($uri, $headers = array()) + public function trace($uri, array $headers = array()) { return $this->request( 'TRACE', @@ -232,7 +232,7 @@ public function getStack() * @throws BadResponseException HTTP error (4xx or 5xx response) * @throws RuntimeException Failure to create stream */ - public function handle(RequestInterface $request, $options = array()) + public function handle(RequestInterface $request, array $options = array()) { $options = $this->mergeOptions($options); $request = $this->applyOptions($request, $options); @@ -256,12 +256,31 @@ public function handle(RequestInterface $request, $options = array()) * * @return PromiseInterface|ResponseInterface */ - public function request($method, $uri, $options = array()) + public function request($method, $uri, array $options = array()) { $request = $this->factory->request($method, $uri); return $this->handle($request, $options); } + /** + * Assert valid options + * + * @param array $options Options + * + * @return void + * + * @throws InvalidArgumentException + */ + private function assertOptions(array $options) + { + if (\is_array($options['headers']) !== true) { + throw new InvalidArgumentException('headers must be an array'); + } + if (\array_keys($options['headers']) === \range(0, \count($options['headers']) - 1)) { + throw new InvalidArgumentException('headers array must have header name as keys'); + } + } + /** * Merge specified options with default client options * @@ -300,17 +319,10 @@ private function mergeOptions($options) * @param array $options options * * @return RequestInterface - * - * @throws InvalidArgumentException */ - private function applyOptions(RequestInterface $request, $options) + private function applyOptions(RequestInterface $request, array $options) { - if (\is_array($options['headers']) !== true) { - throw new InvalidArgumentException('headers must be an array'); - } - if (\array_keys($options['headers']) === \range(0, \count($options['headers']) - 1)) { - throw new InvalidArgumentException('headers array must have header name as keys'); - } + $this->assertOptions($options); foreach ($options['headers'] as $name => $val) { $request = $request->withHeader($name, $val); diff --git a/src/CurlHttpMessage/ClientAsync.php b/src/CurlHttpMessage/ClientAsync.php index 68d84d15..deea4566 100644 --- a/src/CurlHttpMessage/ClientAsync.php +++ b/src/CurlHttpMessage/ClientAsync.php @@ -63,11 +63,8 @@ public function each($requests, array $config = array()) yield $key => $request($opts); continue; } - throw new InvalidArgumentException( - 'Each value yielded by the iterator must be a' - . ' Psr7\Http\Message\RequestInterface or a callable that returns a promise' - . ' that fulfills with a Psr7\Message\Http\ResponseInterface object.' - ); + throw new InvalidArgumentException('Each request must be a Psr7\Http\Message\RequestInterface' + . ' or a callable that returns a promise that fulfills with a Psr7\Message\Http\ResponseInterface object.'); } }; diff --git a/src/CurlHttpMessage/CurlReqResOptions.php b/src/CurlHttpMessage/CurlReqResOptions.php index ad882215..c85e3565 100644 --- a/src/CurlHttpMessage/CurlReqResOptions.php +++ b/src/CurlHttpMessage/CurlReqResOptions.php @@ -187,12 +187,12 @@ private function setOptionsHttpHeader() private function requestHeadersToHeaders(array $requestHeaders) { $headers = []; - foreach ($requestHeaders as $name => $values) { + \array_walk($requestHeaders, function ($values, $name) use (&$headers) { $nameLower = \strtolower($name); // cURL does not support 'Expect-Continue', skip all 'EXPECT' headers if ($nameLower === 'expect') { - continue; + return; } if ($nameLower === 'content-length') { @@ -207,7 +207,7 @@ private function requestHeadersToHeaders(array $requestHeaders) foreach ($values as $value) { $headers[] = $name . ': ' . $value; } - } + }); return $headers; } diff --git a/src/CurlHttpMessage/Handler/Mock.php b/src/CurlHttpMessage/Handler/Mock.php index 77d2860e..2a69038c 100644 --- a/src/CurlHttpMessage/Handler/Mock.php +++ b/src/CurlHttpMessage/Handler/Mock.php @@ -71,17 +71,7 @@ public function __invoke(CurlReqRes $curlReqRes) $this->lastCurlReqRes = $curlReqRes; - $response = \array_shift($this->queue); - - if (\is_callable($response)) { - $response = $response($curlReqRes->getRequest(), $curlReqRes->getOptions()); - } - - $response = $response instanceof Exception - ? Promise::rejectionFor($response) - : Promise::promiseFor($response); - - return $response->then( + return $this->getResponsePromise()->then( function (ResponseInterface $value) use ($curlReqRes) { $curlReqRes->setResponse($value); @@ -109,8 +99,6 @@ function ($reason) { * @param mixed ...$values Value(s) to add to queue * * @return void - * - * @throws InvalidArgumentException */ public function append($values) { @@ -118,16 +106,7 @@ public function append($values) ? $values : \func_get_args(); foreach ($values as $value) { - $isValid = $value instanceof ResponseInterface - || $value instanceof PromiseInterface - || $value instanceof Exception - || \is_callable($value); - if ($isValid === false) { - throw new InvalidArgumentException(\sprintf( - 'Expected a Response, Promise, Throwable or callable. %s provided', - \is_object($value) ? \get_class($value) : \gettype($value) - )); - } + $this->assertValue($value); $this->queue[] = $value; } } @@ -174,4 +153,48 @@ public function reset() { $this->queue = array(); } + + /** + * Get promise wrapped in promise + * + * @return PromiseInterface + */ + public function getResponsePromise() + { + $response = \array_shift($this->queue); + + if (\is_callable($response)) { + $response = $response( + $this->lastCurlReqRes->getRequest(), + $this->lastCurlReqRes->getOptions() + ); + } + + return $response instanceof Exception + ? Promise::rejectionFor($response) + : Promise::promiseFor($response); + } + + /** + * Assert valid mock "response" value + * + * @param callable|Exception|PromiseInterface|ResponseInterface $value value to assert + * + * @return void + * + * @throws InvalidArgumentException + */ + private function assertValue($value) + { + $isValid = $value instanceof ResponseInterface + || $value instanceof PromiseInterface + || $value instanceof Exception + || \is_callable($value); + if ($isValid === false) { + throw new InvalidArgumentException(\sprintf( + 'Expected a Response, Promise, Throwable or callable. %s provided', + \is_object($value) ? \get_class($value) : \gettype($value) + )); + } + } } diff --git a/src/CurlHttpMessage/Middleware/FollowLocation.php b/src/CurlHttpMessage/Middleware/FollowLocation.php index 327f9242..89455df5 100644 --- a/src/CurlHttpMessage/Middleware/FollowLocation.php +++ b/src/CurlHttpMessage/Middleware/FollowLocation.php @@ -8,6 +8,7 @@ use bdk\HttpMessage\Stream; use bdk\HttpMessage\Uri; use bdk\HttpMessage\Utility\Uri as UriUtils; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; @@ -33,6 +34,113 @@ public function __invoke(callable $handler) }; } + /** + * Assert redirects does not exceed maxRedirect + * + * @param CurlReqRes $curlReqRes CurlReqRes instance + * + * @return void + * + * @throws RequestException + */ + private function assertMax(CurlReqRes $curlReqRes) + { + $count = $curlReqRes->getOption('redirectCount') ?: 1; + if ($count > $curlReqRes->getOption('maxRedirect')) { + throw new RequestException( + 'Too many redirects (' . $count . ')', + $curlReqRes->getRequest(), + $curlReqRes->getResponse() + ); + } + $curlReqRes->setOption('redirectCount', $count + 1); + } + + /** + * Assert redirecting to http or https url + * + * @param CurlReqRes $curlReqRes CurlReqRes instance + * @param UriInterface $uri Uri instance + * + * @return void + * + * @throws BadResponseException + */ + private function assertScheme(CurlReqRes $curlReqRes, UriInterface $uri) + { + $allowed = array('http', 'https'); + if (\in_array($uri->getScheme(), $allowed, true) === false) { + throw new BadResponseException( + \sprintf( + 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s', + $uri, + \implode(', ', $allowed) + ), + $curlReqRes->getRequest(), + $curlReqRes->getResponse() + ); + } + } + + /** + * Handle cross-origin redirect + * + * @param CurlReqRes $curlReqRes CurlReqRes instance + * @param RequestInterface $requestNext Next request + * + * @return RequestInterface + */ + protected function handleCrossOrigin(CurlReqRes $curlReqRes, RequestInterface $requestNext) + { + $requestNext = $requestNext->withoutHeader('Authorization'); + $requestNext = $requestNext->withoutHeader('Cookie'); + + $curlOptions = $curlReqRes->getOption('curl'); + unset( + $curlOptions[CURLOPT_HTTPAUTH], + $curlOptions[CURLOPT_USERPWD] + ); + $curlReqRes->setOption('curl', $curlOptions); + + return $requestNext; + } + + /** + * Handle 301 and 302 response codes + * + * According to the HTTP specs, + * a 303 redirection should be followed usingthe GET method. + * 301 and 302 must not. + * + * @param CurlReqRes $curlReqRes CurlReqRes instance + * @param RequestInterface $requestNext Next request + * + * @return RequestInterface + */ + protected function handleFoundAndMoved(CurlReqRes $curlReqRes, RequestInterface $requestNext) + { + $safeMethods = ['GET', 'HEAD', 'OPTIONS']; + $requestMethod = $requestNext->getMethod(); + + if (\in_array($requestMethod, $safeMethods, true) === false) { + $requestNext = $requestNext->withMethod('GET'); + } + + $requestNext = $requestNext->withoutHeader('Content-Type'); + $requestNext = $requestNext->withBody(new Stream()); + + $curlOptions = $curlReqRes->getOption('curl'); + unset( + $curlOptions[CURLOPT_POSTFIELDS], + $curlOptions[CURLOPT_UPLOAD], + $curlOptions[CURLOPT_INFILESIZE], + $curlOptions[CURLOPT_READFUNCTION] + ); + $curlReqRes->setOption('curl', $curlOptions); + + return $requestNext; + } + /** * Process response and redirect if directed by response * @@ -81,92 +189,18 @@ protected function process(CurlReqRes $curlReqRes) private function updateCurlReqRes(CurlReqRes $curlReqRes, UriInterface $uriNew) { $request = $curlReqRes->getRequest(); - $curlOptions = $curlReqRes->getOption('curl'); $requestNext = $request->withUri($uriNew); if (UriUtils::isCrossOrigin($request->getUri(), $uriNew)) { - unset( - $curlOptions[CURLOPT_HTTPAUTH], - $curlOptions[CURLOPT_USERPWD] - ); - $requestNext = $requestNext->withoutHeader('Authorization'); - $requestNext = $requestNext->withoutHeader('Cookie'); + $requestNext = $this->handleCrossOrigin($curlReqRes, $requestNext); } $statusCode = $curlReqRes->getResponse()->getStatusCode(); - /* - According to the HTTP specs, a 303 redirection should be followed using - the GET method. 301 and 302 must not. - */ if ($statusCode <= 303) { - $safeMethods = ['GET', 'HEAD', 'OPTIONS']; - $requestMethod = $request->getMethod(); - - if (\in_array($requestMethod, $safeMethods, true) === false) { - $requestNext = $requestNext->withMethod('GET'); - } - - $requestNext = $requestNext->withoutHeader('Content-Type'); - $requestNext = $requestNext->withBody(new Stream()); - unset( - $curlOptions[CURLOPT_POSTFIELDS], - $curlOptions[CURLOPT_UPLOAD], - $curlOptions[CURLOPT_INFILESIZE], - $curlOptions[CURLOPT_READFUNCTION] - ); + $requestNext = $this->handleFoundAndMoved($curlReqRes, $requestNext); } - $curlReqRes - ->setRequest($requestNext) - ->setOption('curl', $curlOptions); - } - - /** - * Assert redirects does not exceed maxRedirect - * - * @param CurlReqRes $curlReqRes CurlReqRes instance - * - * @return void - * - * @throws RequestException - */ - private function assertMax(CurlReqRes $curlReqRes) - { - $count = $curlReqRes->getOption('redirectCount') ?: 1; - if ($count > $curlReqRes->getOption('maxRedirect')) { - throw new RequestException( - 'Too many redirects (' . $count . ')', - $curlReqRes->getRequest(), - $curlReqRes->getResponse() - ); - } - $curlReqRes->setOption('redirectCount', $count + 1); - } - - /** - * Assert redirecting to http or https url - * - * @param CurlReqRes $curlReqRes CurlReqRes instance - * @param UriInterface $uri Uri instance - * - * @return void - * - * @throws BadResponseException - */ - private function assertScheme(CurlReqRes $curlReqRes, UriInterface $uri) - { - $allowed = array('http', 'https'); - if (\in_array($uri->getScheme(), $allowed, true) === false) { - throw new BadResponseException( - \sprintf( - 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s', - $uri, - \implode(', ', $allowed) - ), - $curlReqRes->getRequest(), - $curlReqRes->getResponse() - ); - } + $curlReqRes->setRequest($requestNext); } } diff --git a/src/Debug/AbstractDebug.php b/src/Debug/AbstractDebug.php index fa091738..1864312e 100644 --- a/src/Debug/AbstractDebug.php +++ b/src/Debug/AbstractDebug.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -167,15 +167,14 @@ public function onConfig(Event $event) if (!$cfg || !$event['isTarget']) { return; } - $valActions = array( + $valActions = \array_intersect_key(array( 'logServerKeys' => function ($val) { // don't append, replace $this->cfg['logServerKeys'] = array(); return $val; }, 'serviceProvider' => array($this, 'onCfgServiceProvider'), - ); - $valActions = \array_intersect_key($valActions, $cfg); + ), $cfg); foreach ($valActions as $key => $callable) { /** @psalm-suppress TooManyArguments */ $cfg[$key] = $callable($cfg[$key]); diff --git a/src/Debug/Abstraction/AbstractArray.php b/src/Debug/Abstraction/AbstractArray.php index 8c9a77f9..591ffebf 100644 --- a/src/Debug/Abstraction/AbstractArray.php +++ b/src/Debug/Abstraction/AbstractArray.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -15,6 +15,7 @@ use bdk\Debug\AbstractComponent; use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; /** * Abstracter: Methods used de=refrence and store arrays @@ -52,7 +53,7 @@ public function crate(array $array, $method = null, array $hist = array()) return Abstracter::RECURSION; } if ($this->cfg['maxDepth'] && \count($hist) === $this->cfg['maxDepth']) { - return new Abstraction(Abstracter::TYPE_ARRAY, array( + return new Abstraction(Type::TYPE_ARRAY, array( 'options' => array( 'isMaxDepth' => true, ), @@ -77,7 +78,7 @@ public function crate(array $array, $method = null, array $hist = array()) */ public function getAbstraction(array &$array, $method = null, array $hist = array()) { - return new Abstraction(Abstracter::TYPE_ARRAY, array( + return new Abstraction(Type::TYPE_ARRAY, array( 'value' => $this->crate($array, $method, $hist), )); } @@ -97,7 +98,7 @@ public function getCallableAbstraction(array $array) if (PHP_VERSION_ID >= 70000 && \strpos($className, "@anonymous\0") !== false) { $className = $this->abstracter->debug->php->friendlyClassName($array[0]); } - return new Abstraction(Abstracter::TYPE_CALLABLE, array( + return new Abstraction(Type::TYPE_CALLABLE, array( 'value' => array($className, $array[1]), )); } diff --git a/src/Debug/Abstraction/AbstractObject.php b/src/Debug/Abstraction/AbstractObject.php index 9fbcdd6b..eadc73c1 100644 --- a/src/Debug/Abstraction/AbstractObject.php +++ b/src/Debug/Abstraction/AbstractObject.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -269,7 +269,7 @@ private function doAbstraction(ObjectAbstraction $abs) if ($abs['isRecursion']) { return; } - $abs['isTraverseOnly'] = $this->isTraverseOnly($abs); + $abs['isTraverseOnly'] = $this->helper->isTraverseOnly($abs); /* Debug::EVENT_OBJ_ABSTRACT_START subscriber may set isExcluded @@ -409,21 +409,4 @@ private function isObjInList($obj, array $list) } return false; } - - /** - * Test if only need to populate traverseValues - * - * @param ObjectAbstraction $abs Abstraction instance - * - * @return bool - */ - private function isTraverseOnly(ObjectAbstraction $abs) - { - if ($abs['debugMethod'] === 'table' && \count($abs['hist']) < 4) { - $abs['cfgFlags'] &= ~self::CONST_COLLECT; // set collect constants to "false" - $abs['cfgFlags'] &= ~self::METHOD_COLLECT; // set collect methods to "false" - return true; - } - return false; - } } diff --git a/src/Debug/Abstraction/AbstractString.php b/src/Debug/Abstraction/AbstractString.php index 7e5e8eff..2331598b 100644 --- a/src/Debug/Abstraction/AbstractString.php +++ b/src/Debug/Abstraction/AbstractString.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -14,8 +14,8 @@ use bdk\Debug\AbstractComponent; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\HttpMessage\Utility\ContentType; -use finfo; /** * Abstracter: Methods used to abstract objects @@ -51,16 +51,16 @@ public function getAbstraction($string, $typeMore = null, $crateVals = array()) { $absValues = $this->absValuesInit($string, $typeMore); switch ($typeMore) { - case Abstracter::TYPE_STRING_BASE64: + case Type::TYPE_STRING_BASE64: $absValues = $this->getAbsValuesBase64($absValues); break; - case Abstracter::TYPE_STRING_BINARY: + case Type::TYPE_STRING_BINARY: $absValues = $this->getAbsValuesBinary($absValues); break; - case Abstracter::TYPE_STRING_JSON: + case Type::TYPE_STRING_JSON: $absValues = $this->getAbsValuesJson($absValues, $crateVals); break; - case Abstracter::TYPE_STRING_SERIALIZED: + case Type::TYPE_STRING_SERIALIZED: $absValues = $this->getAbsValuesSerialized($absValues); break; default: @@ -69,7 +69,7 @@ public function getAbstraction($string, $typeMore = null, $crateVals = array()) } } $absValues = $this->absValuesFinish($absValues); - return new Abstraction(Abstracter::TYPE_STRING, $absValues); + return new Abstraction(Type::TYPE_STRING, $absValues); } /** @@ -82,20 +82,20 @@ public function getAbstraction($string, $typeMore = null, $crateVals = array()) public function getType($val) { $debugVals = array( - Abstracter::NOT_INSPECTED => Abstracter::TYPE_NOT_INSPECTED, - Abstracter::RECURSION => Abstracter::TYPE_RECURSION, - Abstracter::UNDEFINED => Abstracter::TYPE_UNDEFINED, + Abstracter::NOT_INSPECTED => Type::TYPE_NOT_INSPECTED, + Abstracter::RECURSION => Type::TYPE_RECURSION, + Abstracter::UNDEFINED => Type::TYPE_UNDEFINED, ); if (isset($debugVals[$val])) { return array($debugVals[$val], null); } if (\is_numeric($val) === false) { - return $this->getTypeMore($val); + return array(Type::TYPE_STRING, $this->getTypeMore($val)); } - $typeMore = $this->abstracter->testTimestamp($val) - ? Abstracter::TYPE_TIMESTAMP - : Abstracter::TYPE_STRING_NUMERIC; - return array(Abstracter::TYPE_STRING, $typeMore); + $typeMore = $this->abstracter->type->isTimestamp($val) + ? Type::TYPE_TIMESTAMP + : Type::TYPE_STRING_NUMERIC; + return array(Type::TYPE_STRING, $typeMore); } /** @@ -109,13 +109,13 @@ public function getType($val) private function absValuesFinish(array $absValues) { $typeMore = $absValues['typeMore']; - if ($absValues['brief'] && $typeMore !== Abstracter::TYPE_STRING_BINARY) { + if ($absValues['brief'] && $typeMore !== Type::TYPE_STRING_BINARY) { $matches = array(); $regex = '/^([^\r\n]{1,' . ($absValues['maxlen'] ?: 128) . '})/'; \preg_match($regex, $absValues['value'], $matches); $absValues['value'] = $matches[1]; } - if ($absValues['strlen'] === \strlen($absValues['value']) && $typeMore !== Abstracter::TYPE_STRING_BINARY) { + if ($absValues['strlen'] === \strlen($absValues['value']) && $typeMore !== Type::TYPE_STRING_BINARY) { // including strlen indicates that value was truncated // (strlen is always provided for binary) $absValues['strlen'] = null; @@ -137,10 +137,10 @@ private function absValuesFinish(array $absValues) protected function absValuesInit($string, $typeMore) { $maxLenCats = array( - Abstracter::TYPE_STRING_BASE64 => 'base64', - Abstracter::TYPE_STRING_BINARY => 'binary', - Abstracter::TYPE_STRING_JSON => 'json', - Abstracter::TYPE_STRING_SERIALIZED => 'other', + Type::TYPE_STRING_BASE64 => 'base64', + Type::TYPE_STRING_BINARY => 'binary', + Type::TYPE_STRING_JSON => 'json', + Type::TYPE_STRING_SERIALIZED => 'other', ); $maxLenCat = isset($maxLenCats[$typeMore]) ? $maxLenCats[$typeMore] @@ -275,7 +275,7 @@ private function getMaxLen($cat, $strlen) * * @param string $val string value * - * @return array type and typeMore + * @return string|null */ private function getTypeMore($val) { @@ -286,17 +286,16 @@ private function getTypeMore($val) $typeMore = $this->getTypeStringEncoded($val); } if ($typeMore) { - return array(Abstracter::TYPE_STRING, $typeMore); + return $typeMore; } if ($this->debug->utf8->isUtf8($val) === false) { - $typeMore = Abstracter::TYPE_STRING_BINARY; - return array(Abstracter::TYPE_STRING, $typeMore); + return Type::TYPE_STRING_BINARY; } $maxlen = $this->getMaxLen('other', $strLen); if ($maxlen > -1 && $strLen > $maxlen) { - $typeMore = Abstracter::TYPE_STRING_LONG; + return Type::TYPE_STRING_LONG; } - return array(Abstracter::TYPE_STRING, $typeMore); + return null; } /** @@ -309,13 +308,13 @@ private function getTypeMore($val) private function getTypeStringEncoded($val) { if ($this->debug->stringUtil->isBase64Encoded($val)) { - return Abstracter::TYPE_STRING_BASE64; + return Type::TYPE_STRING_BASE64; } if ($this->debug->stringUtil->isJson($val)) { - return Abstracter::TYPE_STRING_JSON; + return Type::TYPE_STRING_JSON; } if ($this->debug->stringUtil->isSerializedSafe($val)) { - return Abstracter::TYPE_STRING_SERIALIZED; + return Type::TYPE_STRING_SERIALIZED; } return null; } diff --git a/src/Debug/Abstraction/Abstracter.php b/src/Debug/Abstraction/Abstracter.php index ae6ae500..e02115f5 100644 --- a/src/Debug/Abstraction/Abstracter.php +++ b/src/Debug/Abstraction/Abstracter.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -17,7 +17,7 @@ use bdk\Debug\Abstraction\AbstractArray; use bdk\Debug\Abstraction\Abstraction; use bdk\Debug\Abstraction\AbstractObject; -use bdk\Debug\Utility\Php; +use bdk\Debug\Abstraction\Type; /** * Store array/object/resource info @@ -29,47 +29,17 @@ class Abstracter extends AbstractComponent const RECURSION = "\x00recursion\x00"; // ie, array recursion const UNDEFINED = "\x00undefined\x00"; - const TYPE_ARRAY = 'array'; - const TYPE_BOOL = 'bool'; - const TYPE_CALLABLE = 'callable'; // non-native type : callable array - const TYPE_INT = 'int'; - const TYPE_FLOAT = 'float'; - const TYPE_NULL = 'null'; - const TYPE_OBJECT = 'object'; - const TYPE_RESOURCE = 'resource'; - const TYPE_STRING = 'string'; - const TYPE_CONST = 'const'; // non-native type (Abstraction: we store name and value) - const TYPE_NOT_INSPECTED = 'notInspected'; // non-native type - const TYPE_RECURSION = 'recursion'; // non-native type - const TYPE_UNDEFINED = 'undefined'; // non-native type - const TYPE_UNKNOWN = 'unknown'; // non-native type - - /* - "typeMore" values - (no constants for "true" & "false") - */ - const TYPE_ABSTRACTION = 'abstraction'; - const TYPE_RAW = 'raw'; // raw object or array - const TYPE_FLOAT_INF = "\x00inf\x00"; - const TYPE_FLOAT_NAN = "\x00nan\x00"; - const TYPE_STRING_BASE64 = 'base64'; - const TYPE_STRING_BINARY = 'binary'; - const TYPE_STRING_CLASSNAME = 'classname'; - const TYPE_STRING_JSON = 'json'; - const TYPE_STRING_LONG = 'maxLen'; - const TYPE_STRING_NUMERIC = 'numeric'; - const TYPE_STRING_SERIALIZED = 'serialized'; - const TYPE_TIMESTAMP = 'timestamp'; - protected $abstractArray; protected $abstractObject; protected $abstractString; protected $debug; + protected $type; protected $readOnly = array( 'abstractArray', 'abstractObject', 'abstractString', 'debug', + 'type', ); protected $cfg = array( 'brief' => false, // collect & output less details @@ -127,6 +97,7 @@ public function __construct(Debug $debug, $cfg = array()) $this->abstractArray = new AbstractArray($this); $this->abstractObject = new AbstractObject($this); $this->abstractString = new AbstractString($this); + $this->type = new Type($this); $this->cfg = \array_merge( $this->cfg, \array_fill_keys( @@ -157,7 +128,7 @@ public function crate($mixed, $method = null, $hist = array()) if (!$typeInfo) { return $mixed; } - return $typeInfo === array(self::TYPE_ARRAY, self::TYPE_RAW) + return $typeInfo === array(Type::TYPE_ARRAY, Type::TYPE_RAW) ? $this->abstractArray->crate($mixed, $method, $hist) : $this->getAbstraction($mixed, $method, $typeInfo, $hist); } @@ -210,23 +181,23 @@ public function crateWithVals($mixed, $values = array()) */ public function getAbstraction($val, $method = null, $typeInfo = array(), $hist = array()) { - list($type, $typeMore) = $typeInfo ?: $this->getType($val); + list($type, $typeMore) = $typeInfo ?: $this->type->getType($val); switch ($type) { - case self::TYPE_ARRAY: + case Type::TYPE_ARRAY: return $this->abstractArray->getAbstraction($val, $method, $hist); - case self::TYPE_CALLABLE: + case Type::TYPE_CALLABLE: return $this->abstractArray->getCallableAbstraction($val); - case self::TYPE_FLOAT: + case Type::TYPE_FLOAT: return $this->getAbstractionFloat($val, $typeMore); - case self::TYPE_OBJECT: + case Type::TYPE_OBJECT: return $val instanceof \SensitiveParameterValue ? $this->abstractString->getAbstraction(\call_user_func($this->debug->getPlugin('redaction')->getCfg('redactReplace'), 'redacted')) : $this->abstractObject->getAbstraction($val, $method, $hist); - case self::TYPE_RESOURCE: + case Type::TYPE_RESOURCE: return new Abstraction($type, array( 'value' => \print_r($val, true) . ': ' . \get_resource_type($val), )); - case self::TYPE_STRING: + case Type::TYPE_STRING: return $this->abstractString->getAbstraction($val, $typeMore, $this->crateVals); default: return new Abstraction($type, array( @@ -236,44 +207,6 @@ public function getAbstraction($val, $method = null, $typeInfo = array(), $hist } } - /** - * Returns value's type and "extended type" (ie "numeric", "binary", etc) - * - * @param mixed $val value - * - * @return array [$type, $typeMore] typeMore may be - * null - * 'raw' indicates value needs crating - * 'abstraction' - * 'true' (type bool) - * 'false' (type bool) - * 'numeric' (type string) - */ - public function getType($val) - { - $type = self::getTypePhp($val); - switch ($type) { - case self::TYPE_ARRAY: - return $this->getTypeArray($val); - case self::TYPE_BOOL: - return array(self::TYPE_BOOL, \json_encode($val)); - case self::TYPE_FLOAT: - return $this->getTypeFloat($val); - case self::TYPE_INT: - return $this->getTypeInt($val); - case self::TYPE_OBJECT: - return $this->getTypeObject($val); - case self::TYPE_RESOURCE: - return array(self::TYPE_RESOURCE, self::TYPE_RAW); - case self::TYPE_STRING: - return $this->abstractString->getType($val); - case self::TYPE_UNKNOWN: - return $this->getTypeUnknown($val); - default: - return array($type, null); - } - } - /** * Is the passed value an abstraction * @@ -307,11 +240,11 @@ public function needsAbstraction($val) if ($val instanceof Abstraction) { return false; } - list($type, $typeMore) = $this->getType($val); - if ($type === self::TYPE_BOOL) { + list($type, $typeMore) = $this->type->getType($val); + if ($type === Type::TYPE_BOOL) { return false; } - if (\in_array($typeMore, array(self::TYPE_ABSTRACTION, self::TYPE_STRING_NUMERIC), true)) { + if (\in_array($typeMore, array(Type::TYPE_ABSTRACTION, Type::TYPE_STRING_NUMERIC), true)) { return false; } return $typeMore @@ -319,20 +252,6 @@ public function needsAbstraction($val) : false; } - /** - * Does value appear to be a unix timestamp? - * - * @param int|string|float $val Value to test - * - * @return bool - */ - public function testTimestamp($val) - { - $secs = 86400 * 90; // 90 days worth o seconds - $tsNow = \time(); - return $val > $tsNow - $secs && $val < $tsNow + $secs; - } - /** * Abstract a float * @@ -345,141 +264,17 @@ public function testTimestamp($val) */ private function getAbstractionFloat($val, $typeMore) { - if ($typeMore === self::TYPE_FLOAT_INF) { - $val = self::TYPE_FLOAT_INF; - } elseif ($typeMore === self::TYPE_FLOAT_NAN) { - $val = self::TYPE_FLOAT_NAN; + if ($typeMore === Type::TYPE_FLOAT_INF) { + $val = Type::TYPE_FLOAT_INF; + } elseif ($typeMore === Type::TYPE_FLOAT_NAN) { + $val = Type::TYPE_FLOAT_NAN; } - return new Abstraction(self::TYPE_FLOAT, array( + return new Abstraction(Type::TYPE_FLOAT, array( 'typeMore' => $typeMore, 'value' => $val, )); } - /** - * Get Array's type & typeMore - * - * @param array $val array value - * - * @return array - */ - private function getTypeArray($val) - { - $type = self::TYPE_ARRAY; - $typeMore = self::TYPE_RAW; // needs abstracted (references removed / values abstracted if necessary) - if (\count($val) === 2 && $this->debug->php->isCallable($val, Php::IS_CALLABLE_ARRAY_ONLY)) { - $type = self::TYPE_CALLABLE; - } - return array($type, $typeMore); - } - - /** - * Get Float's type & typeMore - * - * INF and NAN are considered "float" - * - * @param float $val float/INF/NAN - * - * @return array - */ - private function getTypeFloat($val) - { - $typeMore = null; - if ($val === INF) { - $typeMore = self::TYPE_FLOAT_INF; - } elseif (\is_nan($val)) { - // using is_nan() func as comparing with NAN constant doesn't work - $typeMore = self::TYPE_FLOAT_NAN; - } elseif ($this->testTimestamp($val)) { - $typeMore = self::TYPE_TIMESTAMP; - } - return array(self::TYPE_FLOAT, $typeMore); - } - - /** - * Get Int's type & typeMore - * - * INF and NAN are considered "float" - * - * @param float $val float/INF/NAN - * - * @return array - */ - private function getTypeInt($val) - { - $typeMore = $this->testTimestamp($val) - ? self::TYPE_TIMESTAMP - : null; - return array(self::TYPE_INT, $typeMore); - } - - /** - * Get Object's type & typeMore - * - * @param object $object any object - * - * @return array type & typeMore - */ - private function getTypeObject($object) - { - $type = self::TYPE_OBJECT; - $typeMore = self::TYPE_RAW; // needs abstracted - if ($object instanceof Abstraction) { - $type = $object['type']; - $typeMore = self::TYPE_ABSTRACTION; - } - return array($type, $typeMore); - } - - /** - * Get PHP type - * - * @param mixed` $val value - * - * @return string "array", "bool", "float", "int", "null", "object", "resource", "string", "unknown" - */ - private static function getTypePhp($val) - { - $type = \gettype($val); - $map = array( - 'boolean' => self::TYPE_BOOL, - 'double' => self::TYPE_FLOAT, - 'integer' => self::TYPE_INT, - 'NULL' => self::TYPE_NULL, - 'resource (closed)' => self::TYPE_RESOURCE, - 'unknown type' => self::TYPE_UNKNOWN, // closed resource (php < 7.2) - ); - if (isset($map[$type])) { - $type = $map[$type]; - } - return $type; - } - - /** - * Get "unknown" type & typeMore - * - * @param mixed $val value of unknown type (likely closed resource) - * - * @return array type and typeMore - * - * @SuppressWarnings(PHPMD.DevelopmentCodeFragment) - */ - private function getTypeUnknown($val) - { - $type = self::TYPE_UNKNOWN; - $typeMore = null; - /* - closed resource? - is_resource() returns false for a closed resource - gettype returns 'unknown type' or 'resource (closed)' - */ - if (\strpos(\print_r($val, true), 'Resource') === 0) { - $type = self::TYPE_RESOURCE; - $typeMore = self::TYPE_RAW; // needs abstracted - } - return array($type, $typeMore); - } - /** * {@inheritDoc} */ diff --git a/src/Debug/Abstraction/Abstraction.php b/src/Debug/Abstraction/Abstraction.php index c8d0d3c3..88a14ca7 100644 --- a/src/Debug/Abstraction/Abstraction.php +++ b/src/Debug/Abstraction/Abstraction.php @@ -6,13 +6,14 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Abstraction; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\PubSub\Event; /** @@ -35,9 +36,9 @@ class Abstraction extends Event public function __construct($type, $values = array()) { $values['type'] = $type; - if ($type !== Abstracter::TYPE_OBJECT && \array_key_exists('value', $values) === false) { + if ($type !== Type::TYPE_OBJECT && \array_key_exists('value', $values) === false) { // make sure non-object gets value - $values['value'] = $type === Abstracter::TYPE_ARRAY + $values['value'] = $type === Type::TYPE_ARRAY ? array() : null; } diff --git a/src/Debug/Abstraction/Object/Inheritable.php b/src/Debug/Abstraction/Object/AbstractInheritable.php similarity index 97% rename from src/Debug/Abstraction/Object/Inheritable.php rename to src/Debug/Abstraction/Object/AbstractInheritable.php index 51eedb47..00bddbb4 100644 --- a/src/Debug/Abstraction/Object/Inheritable.php +++ b/src/Debug/Abstraction/Object/AbstractInheritable.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -18,7 +18,7 @@ /** * Base class for collecting constants, properties, & methods */ -abstract class Inheritable +abstract class AbstractInheritable { /** @var Abstracter */ protected $abstracter; diff --git a/src/Debug/Abstraction/Object/Abstraction.php b/src/Debug/Abstraction/Object/Abstraction.php index 77ab1c49..75626f81 100644 --- a/src/Debug/Abstraction/Object/Abstraction.php +++ b/src/Debug/Abstraction/Object/Abstraction.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -14,6 +14,7 @@ use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction as BaseAbstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Utility\ArrayUtil; use bdk\PubSub\ValueStore; @@ -45,7 +46,7 @@ class Abstraction extends BaseAbstraction public function __construct(ValueStore $inherited, $values = array()) { $this->inherited = $inherited; - parent::__construct(Abstracter::TYPE_OBJECT, $values); + parent::__construct(Type::TYPE_OBJECT, $values); } /** @@ -106,7 +107,7 @@ public function jsonSerialize() return $this->getInstanceValues() + array( 'debug' => Abstracter::ABSTRACTION, 'inheritsFrom' => $this->inherited['className'], - 'type' => Abstracter::TYPE_OBJECT, + 'type' => Type::TYPE_OBJECT, ); } diff --git a/src/Debug/Abstraction/Object/Constants.php b/src/Debug/Abstraction/Object/Constants.php index 2dd23c8e..c65cd9f1 100644 --- a/src/Debug/Abstraction/Object/Constants.php +++ b/src/Debug/Abstraction/Object/Constants.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -25,7 +25,7 @@ /** * Get object constant info */ -class Constants extends Inheritable +class Constants extends AbstractInheritable { protected $abs; diff --git a/src/Debug/Abstraction/Object/Definition.php b/src/Debug/Abstraction/Object/Definition.php index 8311dc22..bf443d00 100644 --- a/src/Debug/Abstraction/Object/Definition.php +++ b/src/Debug/Abstraction/Object/Definition.php @@ -6,14 +6,12 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ namespace bdk\Debug\Abstraction\Object; -use bdk\Debug\Abstraction\Abstracter; -use bdk\Debug\Abstraction\Abstraction; use bdk\Debug\Abstraction\AbstractObject; use bdk\Debug\Abstraction\Object\Abstraction as ObjectAbstraction; use bdk\PubSub\ValueStore; diff --git a/src/Debug/Abstraction/Object/Helper.php b/src/Debug/Abstraction/Object/Helper.php index 0c278ddc..952f5bae 100644 --- a/src/Debug/Abstraction/Object/Helper.php +++ b/src/Debug/Abstraction/Object/Helper.php @@ -6,12 +6,15 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ namespace bdk\Debug\Abstraction\Object; +use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\AbstractObject; +use bdk\Debug\Abstraction\Object\Abstraction as ObjectAbstraction; use bdk\Debug\Utility\Php as PhpUtil; use bdk\Debug\Utility\PhpDoc; use ReflectionAttribute; @@ -38,6 +41,34 @@ public function __construct(PhpDoc $phpDoc) $this->phpDoc = $phpDoc; } + /** + * Remove desc & summary if not collecting phpDoc + * + * Easier to collect and then remove vs having logic everywhere + * + * @param Abstraction $abs Object Abstraction instance + * + * @return void + */ + public function clearPhpDoc(Abstraction $abs) + { + if ($abs['cfgFlags'] & AbstractObject::PHPDOC_COLLECT) { + return; + } + $methods = $abs['methods']; + foreach ($methods as &$methodInfo) { + $methodInfo['phpDoc'] = array( + 'desc' => null, + 'summary' => null, + ); + foreach (\array_keys($methodInfo['params']) as $index) { + $methodInfo['params'][$index]['desc'] = null; + } + $methodInfo['return']['desc'] = null; + } + $abs['methods'] = $methods; + } + /** * Get object, constant, property, or method attributes * @@ -100,29 +131,29 @@ public static function getParamType(ReflectionParameter $refParameter) /** * Get parsed PhpDoc * - * @param Reflector $reflector Reflector instance - * @param bool $fullyQualifyPhpDocType Whether to further parse / resolve types + * @param Reflector $reflector Reflector instance + * @param bool $fullyQualifyType Whether to further parse / resolve types * * @return array */ - public function getPhpDoc(Reflector $reflector, $fullyQualifyPhpDocType = false) + public function getPhpDoc(Reflector $reflector, $fullyQualifyType = false) { - return $this->phpDoc->getParsed($reflector, $fullyQualifyPhpDocType); + return $this->phpDoc->getParsed($reflector, $fullyQualifyType); } /** * Get type and description from phpDoc comment for Constant or Property * - * @param Reflector $reflector ReflectionProperty or ReflectionClassConstant property object - * @param bool $fullyQualifyPhpDocType Whether to further parse / resolve types + * @param Reflector $reflector ReflectionProperty or ReflectionClassConstant property object + * @param bool $fullyQualifyType Whether to further parse / resolve types * * @return array */ - public function getPhpDocVar(Reflector $reflector, $fullyQualifyPhpDocType = false) + public function getPhpDocVar(Reflector $reflector, $fullyQualifyType = false) { /** @psalm-suppress NoInterfaceProperties */ $name = $reflector->name; - $phpDoc = $this->getPhpDoc($reflector, $fullyQualifyPhpDocType); + $phpDoc = $this->getPhpDoc($reflector, $fullyQualifyType); $info = array( 'desc' => $phpDoc['summary'], 'type' => null, @@ -149,6 +180,23 @@ public function getPhpDocVar(Reflector $reflector, $fullyQualifyPhpDocType = fal return $info; } + /** + * Test if only need to populate traverseValues + * + * @param ObjectAbstraction $abs Abstraction instance + * + * @return bool + */ + public function isTraverseOnly(ObjectAbstraction $abs) + { + if ($abs['debugMethod'] === 'table' && \count($abs['hist']) < 4) { + $abs['cfgFlags'] &= ~AbstractObject::CONST_COLLECT; // set collect constants to "false" + $abs['cfgFlags'] &= ~AbstractObject::METHOD_COLLECT; // set collect methods to "false" + return true; + } + return false; + } + /** * Get constant/method/property visibility * diff --git a/src/Debug/Abstraction/Object/MethodParams.php b/src/Debug/Abstraction/Object/MethodParams.php index 50acbaab..f8afe016 100644 --- a/src/Debug/Abstraction/Object/MethodParams.php +++ b/src/Debug/Abstraction/Object/MethodParams.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -15,6 +15,7 @@ use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; use bdk\Debug\Abstraction\AbstractObject; +use bdk\Debug\Abstraction\Type; use ReflectionMethod; use ReflectionParameter; use UnitEnum; @@ -33,7 +34,9 @@ class MethodParams 'defaultValue' => Abstracter::UNDEFINED, 'desc' => null, 'isOptional' => false, + 'isPassedByReference' => false, 'isPromoted' => false, + 'isVariadic' => false, 'name' => '', 'type' => null, ); @@ -82,24 +85,26 @@ public static function buildParamValues($values = array()) public function getParams(Abstraction $abs, ReflectionMethod $refMethod, $phpDoc = array()) { $this->abs = $abs; - $params = $this->getParamsReflection($refMethod, $phpDoc); + $phpDocParams = isset($phpDoc['param']) + ? $phpDoc['param'] + : array(); + $phpDocParamsByName = array(); + foreach ($phpDocParams as $info) { + $phpDocParamsByName[$info['name']] = $info; + } + $params = $this->getParamsReflection($refMethod, $phpDocParamsByName); /* Iterate over params only defined via phpDoc */ - $phpDocCount = isset($phpDoc['param']) - ? \count($phpDoc['param']) - : 0; + $phpDocCount = \count($phpDocParams); for ($i = \count($params); $i < $phpDocCount; $i++) { $phpDocParam = $phpDoc['param'][$i]; - $name = '$' . \ltrim($phpDocParam['name'], '$'); - if (\substr($name, -4) === ',...') { - $name = '...' . \substr($name, 0, -4); - } $params[] = $this->buildParamValues(array( 'defaultValue' => $this->phpDocParamValue($phpDocParam, $this->abs['className']), 'desc' => $phpDocParam['desc'], 'isOptional' => true, - 'name' => $name, + 'isVariadic' => $phpDocParam['isVariadic'], + 'name' => $phpDocParam['name'], 'type' => $phpDocParam['type'], )); } @@ -131,12 +136,12 @@ public function getParamsPhpDoc(Abstraction $abs, $parsedMethodTag, $className) /** * Get parameter info from reflection * - * @param ReflectionMethod $refMethod ReflectionMethod instance - * @param array $phpDoc Method's parsed phpDoc comment + * @param ReflectionMethod $refMethod ReflectionMethod instance + * @param array $phpDocParamsByName Method's parsed phpDoc comment * * @return array */ - private function getParamsReflection(ReflectionMethod $refMethod, $phpDoc) + private function getParamsReflection(ReflectionMethod $refMethod, array $phpDocParamsByName) { $params = array(); $collectAttribute = $this->abs['cfgFlags'] & AbstractObject::PARAM_ATTRIBUTE_COLLECT; @@ -146,12 +151,9 @@ private function getParamsReflection(ReflectionMethod $refMethod, $phpDoc) // $refParameter->getDefaultValue() // $refParameter->__toString() }); - foreach ($refMethod->getParameters() as $i => $refParameter) { - $phpDocParam = \array_merge(array( - 'desc' => null, - 'name' => '', - 'type' => null, - ), isset($phpDoc['param'][$i]) ? $phpDoc['param'][$i] : array()); + foreach ($refMethod->getParameters() as $refParameter) { + $name = $refParameter->getName(); + $phpDocParam = $this->phpDocParam($name, $phpDocParamsByName); $params[] = $this->buildParamValues(array( 'attributes' => $collectAttribute ? $this->helper->getAttributes($refParameter) @@ -159,10 +161,12 @@ private function getParamsReflection(ReflectionMethod $refMethod, $phpDoc) 'defaultValue' => $this->getParamDefaultVal($refParameter), 'desc' => $phpDocParam['desc'], 'isOptional' => $refParameter->isOptional(), - 'isPromoted' => PHP_VERSION_ID >= 80000 - ? $refParameter->isPromoted() - : false, - 'name' => $this->getParamName($refParameter, $phpDocParam['name']), + 'isPassedByReference' => $refParameter->isPassedByReference(), + 'isPromoted' => PHP_VERSION_ID >= 80000 && $refParameter->isPromoted(), + 'isVariadic' => PHP_VERSION_ID >= 50600 + ? $refParameter->isVariadic() + : $phpDocParam['isVariadic'], + 'name' => $name, 'type' => $this->getParamTypeHint($refParameter, $phpDocParam['type']), )); } @@ -190,7 +194,7 @@ private function getParamDefaultVal(ReflectionParameter $refParameter) php may return something like self::CONSTANT_NAME hhvm will return WhateverTheClassNameIs::CONSTANT_NAME */ - $defaultValue = new Abstraction(Abstracter::TYPE_CONST, array( + $defaultValue = new Abstraction(Type::TYPE_CONST, array( 'name' => $refParameter->getDefaultValueConstantName(), 'value' => $defaultValue, )); @@ -199,30 +203,6 @@ private function getParamDefaultVal(ReflectionParameter $refParameter) return $defaultValue; } - /** - * Get Parameter "name" - * - * @param ReflectionParameter $refParameter reflectionParameter - * @param string $phpDocName name via phpDoc - * - * @return mixed - */ - private function getParamName(ReflectionParameter $refParameter, $phpDocName) - { - $name = '$' . $refParameter->getName(); - if (\method_exists($refParameter, 'isVariadic') && $refParameter->isVariadic()) { - // php >= 5.6 - $name = '...' . $name; - } elseif (\substr($phpDocName, -4) === ',...') { - // phpDoc indicates variadic... - $name = '...' . $name; - } - if ($refParameter->isPassedByReference()) { - $name = '&' . $name; - } - return $name; - } - /** * Get param typehint * @@ -247,16 +227,16 @@ private function getParamTypeHint(ReflectionParameter $refParameter, $phpDocType * * @return string|Abstraction */ - private function phpDocConstant($defaultValue, $className, $matches) + private function phpDocConstant($defaultValue, $className, array $matches) { if ($matches[1] && \defined($className . '::' . $matches[2])) { // self - $defaultValue = new Abstraction(Abstracter::TYPE_CONST, array( + $defaultValue = new Abstraction(Type::TYPE_CONST, array( 'name' => $matches[0], 'value' => \constant($className . '::' . $matches[2]), )); } elseif (\defined($defaultValue)) { - $defaultValue = new Abstraction(Abstracter::TYPE_CONST, array( + $defaultValue = new Abstraction(Type::TYPE_CONST, array( 'name' => $defaultValue, 'value' => \constant($defaultValue), )); @@ -264,6 +244,23 @@ private function phpDocConstant($defaultValue, $className, $matches) return $defaultValue; } + /** + * Get PhpDoc param info + * + * @param string $name param name + * @param array $phpDocParamsByName [description] + * + * @return array + */ + private function phpDocParam($name, array $phpDocParamsByName) + { + return \array_merge(array( + 'desc' => null, + 'isVariadic' => false, + 'type' => null, + ), isset($phpDocParamsByName[$name]) ? $phpDocParamsByName[$name] : array()); + } + /** * Get defaultValue from phpDoc param * @@ -274,7 +271,7 @@ private function phpDocConstant($defaultValue, $className, $matches) * * @return mixed */ - private function phpDocParamValue($param, $className = null) + private function phpDocParamValue(array $param, $className = null) { if (\array_key_exists('defaultValue', $param) === false) { return Abstracter::UNDEFINED; diff --git a/src/Debug/Abstraction/Object/Methods.php b/src/Debug/Abstraction/Object/Methods.php index 5a7b59ac..b64ed0fc 100644 --- a/src/Debug/Abstraction/Object/Methods.php +++ b/src/Debug/Abstraction/Object/Methods.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -23,8 +23,12 @@ /** * Get object method info */ -class Methods extends Inheritable +class Methods extends AbstractInheritable { + protected $methods = array(); + protected $methodsWithStatic = array(); + + /** @var MethodParams */ protected $params; private static $baseMethodInfo = array( @@ -71,8 +75,8 @@ public function __construct(AbstractObject $abstractObject) public function add(Abstraction $abs) { $abs['cfgFlags'] & AbstractObject::METHOD_COLLECT - ? $this->addMethodsFull($abs) - : $this->addMethodsMin($abs); + ? $this->addFull($abs) + : $this->addMin($abs); if (isset($abs['methods']['__toString'])) { $abs['methods']['__toString']['returnValue'] = null; } @@ -107,7 +111,7 @@ public function addInstance(Abstraction $abs) * * @return array */ - public static function buildMethodValues($values = array()) + public static function buildMethodValues(array $values = array()) { return \array_merge(static::$baseMethodInfo, $values); } @@ -149,28 +153,15 @@ public function toString(Abstraction $abs) * * @return void */ - private function addMethodsFull(Abstraction $abs) + private function addFull(Abstraction $abs) { $briefBak = $this->abstracter->debug->setCfg('brief', true, Debug::CONFIG_NO_PUBLISH); - $this->addViaReflection($abs); + $this->addViaRef($abs); $this->abstracter->debug->setCfg('brief', $briefBak, Debug::CONFIG_NO_PUBLISH | Debug::CONFIG_NO_RETURN); $this->addViaPhpDoc($abs); $this->addImplements($abs); - if ($abs['cfgFlags'] & AbstractObject::PHPDOC_COLLECT) { - return; - } - $methods = $abs['methods']; - foreach ($methods as &$methodInfo) { - $methodInfo['phpDoc'] = array( - 'desc' => null, - 'summary' => null, - ); - foreach (\array_keys($methodInfo['params']) as $index) { - $methodInfo['params'][$index]['desc'] = null; - } - $methodInfo['return']['desc'] = null; - } - $abs['methods'] = $methods; + $this->helper->clearPhpDoc($abs); + \ksort($abs['methods']); } /** @@ -180,7 +171,7 @@ private function addMethodsFull(Abstraction $abs) * * @return void */ - private function addMethodsMin(Abstraction $abs) + private function addMin(Abstraction $abs) { $reflector = $abs['reflector']; if ($reflector->hasMethod('__toString')) { @@ -197,7 +188,7 @@ private function addMethodsMin(Abstraction $abs) } /** - * Add `implements` value to common interface methods + * Add `implements` value to interface methods * * @param Abstraction $abs Object Abstraction instance * @@ -249,10 +240,40 @@ private function addViaPhpDoc(Abstraction $abs) return; } foreach ($phpDoc['method'] as $phpDocMethod) { - $abs['methods'][$phpDocMethod['name']] = $this->buildMethodPhpDoc($abs, $phpDocMethod, $declaredLast); + $abs['methods'][$phpDocMethod['name']] = $this->addViaPhpDocBuild($abs, $phpDocMethod, $declaredLast); } } + /** + * Build magic method info + * + * @param Abstraction $abs Object Abstraction instance + * @param array $phpDoc Parsed phpdoc method info + * @param string $declaredLast class-name or null + * + * @return array + */ + private function addViaPhpDocBuild(Abstraction $abs, array $phpDoc, $declaredLast) + { + $className = $declaredLast + ? $declaredLast + : $abs['className']; + return $this->buildMethodValues(array( + 'declaredLast' => $declaredLast, + 'isStatic' => $phpDoc['static'], + 'params' => $this->params->getParamsPhpDoc($abs, $phpDoc, $className), + 'phpDoc' => array( + 'desc' => null, + 'summary' => $phpDoc['desc'], + ), + 'return' => array( + 'desc' => null, + 'type' => $phpDoc['type'], + ), + 'visibility' => 'magic', + )); + } + /** * Inspect inherited classes until we find methods defined in PhpDoc * @@ -282,71 +303,54 @@ private function addViaPhpDocInherit(Abstraction $abs, &$declaredLast = null) * * @return void */ - private function addViaReflection(Abstraction $abs) + private function addViaRef(Abstraction $abs) { - $methods = array(); - $withStaticVars = array(); - $this->traverseAncestors($abs['reflector'], function (ReflectionClass $reflector) use ($abs, &$methods, &$withStaticVars) { + $this->methods = array(); + $this->methodsWithStatic = array(); + $this->traverseAncestors($abs['reflector'], function (ReflectionClass $reflector) use ($abs) { $className = $this->helper->getClassName($reflector); - $refMethods = $reflector->getMethods(); - while ($refMethods) { - $refMethod = \array_pop($refMethods); - $name = $refMethod->getName(); - $info = isset($methods[$name]) - ? $methods[$name] - : $this->buildMethodRef($abs, $refMethod); - $info = $this->updateDeclarationVals( - $info, - $this->helper->getClassName($refMethod->getDeclaringClass()), - $className - ); - $isInherited = $info['declaredLast'] && $info['declaredLast'] !== $abs['className']; - if ($info['visibility'] === 'private' && $isInherited) { - // getMethods() returns parent's private methods (#reasons).. we'll skip it - continue; - } - if (!empty($info['hasStaticVars'])) { - $withStaticVars[] = $name; - } - unset($info['hasStaticVars']); - unset($info['phpDoc']['param']); - unset($info['phpDoc']['return']); - $methods[$name] = $info; + foreach ($reflector->getMethods() as $refMethod) { + $this->addViaRefBuild($abs, $refMethod, $className); } }); - \ksort($methods); - $abs['methodsWithStaticVars'] = $withStaticVars; - $abs['methods'] = $methods; + $abs['methods'] = $this->methods; + $methodsWithStatic = \array_unique($this->methodsWithStatic); + \sort($methodsWithStatic); + $abs['methodsWithStaticVars'] = $methodsWithStatic; } /** - * Build magic method info + * Add/Update method info * - * @param Abstraction $abs Object Abstraction instance - * @param array $phpDoc parsed phpdoc method info - * @param string $declaredLast class-name or null + * @param Abstraction $abs Object Abstraction instance + * @param ReflectionMethod $refMethod ReflectionMethod instance + * @param string $className Current level className * - * @return array + * @return void */ - private function buildMethodPhpDoc(Abstraction $abs, $phpDoc, $declaredLast) + private function addViaRefBuild(Abstraction $abs, ReflectionMethod $refMethod, $className) { - $className = $declaredLast - ? $declaredLast - : $abs['className']; - return $this->buildMethodValues(array( - 'declaredLast' => $declaredLast, - 'isStatic' => $phpDoc['static'], - 'params' => $this->params->getParamsPhpDoc($abs, $phpDoc, $className), - 'phpDoc' => array( - 'desc' => null, - 'summary' => $phpDoc['desc'], - ), - 'return' => array( - 'desc' => null, - 'type' => $phpDoc['type'], - ), - 'visibility' => 'magic', - )); + $name = $refMethod->getName(); + $info = isset($this->methods[$name]) + ? $this->methods[$name] + : $this->addViaRefBuildInit($abs, $refMethod); + $info = $this->updateDeclarationVals( + $info, + $this->helper->getClassName($refMethod->getDeclaringClass()), + $className + ); + $isInherited = $info['declaredLast'] && $info['declaredLast'] !== $abs['className']; + if ($info['visibility'] === 'private' && $isInherited) { + // getMethods() returns parent's private methods (#reasons).. we'll skip it + return; + } + if (!empty($info['hasStaticVars'])) { + $this->methodsWithStatic[] = $name; + } + unset($info['hasStaticVars']); + unset($info['phpDoc']['param']); + unset($info['phpDoc']['return']); + $this->methods[$name] = $info; } /** @@ -357,7 +361,7 @@ private function buildMethodPhpDoc(Abstraction $abs, $phpDoc, $declaredLast) * * @return array */ - private function buildMethodRef(Abstraction $abs, ReflectionMethod $refMethod) + private function addViaRefBuildInit(Abstraction $abs, ReflectionMethod $refMethod) { $phpDoc = $this->helper->getPhpDoc($refMethod, $abs['fullyQualifyPhpDocType']); return $this->buildMethodValues(array( diff --git a/src/Debug/Abstraction/Object/Properties.php b/src/Debug/Abstraction/Object/Properties.php index 8539e39d..7620aeec 100644 --- a/src/Debug/Abstraction/Object/Properties.php +++ b/src/Debug/Abstraction/Object/Properties.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -16,13 +16,14 @@ use bdk\Debug\Abstraction\Abstraction; use bdk\Debug\Abstraction\AbstractObject; use bdk\Debug\Abstraction\Object\PropertiesPhpDoc; +use bdk\Debug\Abstraction\Type; use ReflectionClass; use ReflectionProperty; /** * Get object property info */ -class Properties extends Inheritable +class Properties extends AbstractInheritable { private static $basePropInfo = array( 'attributes' => array(), @@ -81,13 +82,13 @@ public function add(Abstraction $abs) if ($abs['isAnonymous']) { $properties['debug.file'] = static::buildPropValues(array( - 'type' => Abstracter::TYPE_STRING, + 'type' => Type::TYPE_STRING, 'value' => $abs['definition']['fileName'], 'valueFrom' => 'debug', 'visibility' => 'debug', )); $properties['debug.line'] = static::buildPropValues(array( - 'type' => Abstracter::TYPE_INT, + 'type' => Type::TYPE_INT, 'value' => (int) $abs['definition']['startLine'], 'valueFrom' => 'debug', 'visibility' => 'debug', @@ -212,9 +213,7 @@ private function addValues(Abstraction $abs) $valuedProps = array(); $this->traverseAncestors($abs['reflector'], function (ReflectionClass $reflector) use ($abs, &$properties, &$valuedProps) { $className = $this->helper->getClassName($reflector); - $refProperties = $reflector->getProperties(); - while ($refProperties) { - $refProperty = \array_pop($refProperties); + foreach ($reflector->getProperties() as $refProperty) { $name = $refProperty->getName(); if (\in_array($name, $valuedProps, true)) { continue; @@ -222,24 +221,39 @@ private function addValues(Abstraction $abs) $valuedProps[] = $name; $propInfo = isset($properties[$name]) ? $properties[$name] // defined in class - : $this->buildViaRef($abs, $refProperty); - if ($abs['isAnonymous'] && $refProperty->isDefault() && $className === $abs['className']) { - // Necessary for anonymous classes - $propInfo = $this->updateDeclarationVals( - $propInfo, - $this->helper->getClassName($refProperty->getDeclaringClass()), - $className - ); - } - if ($abs['collectPropertyValues']) { - $propInfo = $this->addValue($propInfo, $abs, $refProperty); - } - $properties[$name] = $propInfo; + : $this->buildViaRef($abs, $refProperty); // dynamic + $properties[$name] = $this->addValuesPropInfo($abs, $refProperty, $propInfo, $className); } }); $abs['properties'] = $properties; } + /** + * Update property info with current value / declaration info + * + * @param Abstraction $abs Object Abstraction instance + * @param ReflectionProperty $refProperty ReflectionProperty instance + * @param array $propInfo Property info + * @param string $className Current level className + * + * @return array updated property info + */ + private function addValuesPropInfo(Abstraction $abs, ReflectionProperty $refProperty, array $propInfo, $className) + { + if ($abs['isAnonymous'] && $refProperty->isDefault() && $className === $abs['className']) { + // Necessary for anonymous classes + $propInfo = $this->updateDeclarationVals( + $propInfo, + $this->helper->getClassName($refProperty->getDeclaringClass()), + $className + ); + } + if ($abs['collectPropertyValues']) { + $propInfo = $this->addValue($propInfo, $abs, $refProperty); + } + return $propInfo; + } + /** * Add 'value' and 'valueFrom' values to property info * @@ -288,9 +302,7 @@ private function addViaRef(Abstraction $abs) $properties = $abs['properties']; $this->traverseAncestors($abs['reflector'], function (ReflectionClass $reflector) use ($abs, &$properties) { $className = $this->helper->getClassName($reflector); - $refProperties = $reflector->getProperties(); - while ($refProperties) { - $refProperty = \array_pop($refProperties); + foreach ($reflector->getProperties() as $refProperty) { if ($refProperty->isDefault() === false) { continue; } diff --git a/src/Debug/Abstraction/Object/PropertiesDom.php b/src/Debug/Abstraction/Object/PropertiesDom.php index 77863f5d..cb28e175 100644 --- a/src/Debug/Abstraction/Object/PropertiesDom.php +++ b/src/Debug/Abstraction/Object/PropertiesDom.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -109,8 +109,7 @@ public function add(Abstraction $abs) foreach ($props as $propName => $type) { $val = $obj->{$propName}; if (!$type) { - // function array dereferencing = php 5.4 - $type = $this->abstracter->getType($val)[0]; + $type = $this->abstracter->type->getType($val)[0]; } $abs['properties'][$propName] = Properties::buildPropValues(array( 'type' => $type, diff --git a/src/Debug/Abstraction/Object/Subscriber.php b/src/Debug/Abstraction/Object/Subscriber.php index 4de7812f..a72ee6fc 100644 --- a/src/Debug/Abstraction/Object/Subscriber.php +++ b/src/Debug/Abstraction/Object/Subscriber.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -17,6 +17,7 @@ use bdk\Debug\Abstraction\AbstractObject; use bdk\Debug\Abstraction\Object\Definition as AbstractObjectDefinition; use bdk\Debug\Abstraction\Object\PropertiesDom; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Data; use bdk\Debug\Utility\PhpDoc; use bdk\PubSub\SubscriberInterface; @@ -189,13 +190,13 @@ private function onStartClosure(Abstraction $abs) ); $properties = $abs['properties']; $properties['debug.file'] = $this->abstractObject->properties->buildPropValues(array( - 'type' => Abstracter::TYPE_STRING, + 'type' => Type::TYPE_STRING, 'value' => $abs['definition']['fileName'], 'valueFrom' => 'debug', 'visibility' => 'debug', )); $properties['debug.line'] = $this->abstractObject->properties->buildPropValues(array( - 'type' => Abstracter::TYPE_INT, + 'type' => Type::TYPE_INT, 'value' => (int) $abs['definition']['startLine'], 'valueFrom' => 'debug', 'visibility' => 'debug', @@ -244,7 +245,7 @@ private function promoteParamDescs(Abstraction $abs) } foreach ($abs['methods']['__construct']['params'] as $info) { if ($info['isPromoted'] && $info['desc']) { - $paramName = \substr($info['name'], 1); // toss the "$" + $paramName = $info['name']; $abs['properties'][$paramName]['desc'] = $info['desc']; } } diff --git a/src/Debug/Abstraction/Type.php b/src/Debug/Abstraction/Type.php new file mode 100644 index 00000000..d21469a5 --- /dev/null +++ b/src/Debug/Abstraction/Type.php @@ -0,0 +1,243 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\Debug\Abstraction; + +use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Utility\Php; + +/** + * Determine value type / extended type + */ +class Type +{ + const TYPE_ARRAY = 'array'; + const TYPE_BOOL = 'bool'; + const TYPE_CALLABLE = 'callable'; // non-native type : callable array + const TYPE_INT = 'int'; + const TYPE_FLOAT = 'float'; + const TYPE_NULL = 'null'; + const TYPE_OBJECT = 'object'; + const TYPE_RESOURCE = 'resource'; + const TYPE_STRING = 'string'; + const TYPE_CONST = 'const'; // non-native type (Abstraction: we store name and value) + const TYPE_NOT_INSPECTED = 'notInspected'; // non-native type + const TYPE_RECURSION = 'recursion'; // non-native type + const TYPE_UNDEFINED = 'undefined'; // non-native type + const TYPE_UNKNOWN = 'unknown'; // non-native type + + /* + "typeMore" values + (no constants for "true" & "false") + */ + const TYPE_ABSTRACTION = 'abstraction'; + const TYPE_RAW = 'raw'; // raw object or array + const TYPE_FLOAT_INF = "\x00inf\x00"; + const TYPE_FLOAT_NAN = "\x00nan\x00"; + const TYPE_STRING_BASE64 = 'base64'; + const TYPE_STRING_BINARY = 'binary'; + const TYPE_STRING_CLASSNAME = 'classname'; + const TYPE_STRING_JSON = 'json'; + const TYPE_STRING_LONG = 'maxLen'; + const TYPE_STRING_NUMERIC = 'numeric'; + const TYPE_STRING_SERIALIZED = 'serialized'; + const TYPE_TIMESTAMP = 'timestamp'; + + protected $abstracter; + + /** + * Constructor + * + * @param Abstracter $abstracter Abstracter instance + */ + public function __construct(Abstracter $abstracter) + { + $this->abstracter = $abstracter; + } + + /** + * Returns value's type and "extended type" (ie "numeric", "binary", etc) + * + * @param mixed $val value + * + * @return array [$type, $typeMore] typeMore may be + * null + * 'raw' indicates value needs crating + * 'abstraction' + * 'true' (type bool) + * 'false' (type bool) + * 'numeric' (type string) + */ + public function getType($val) + { + $type = self::getTypePhp($val); + switch ($type) { + case self::TYPE_ARRAY: + return $this->getTypeArray($val); + case self::TYPE_BOOL: + return array(self::TYPE_BOOL, \json_encode($val)); + case self::TYPE_FLOAT: + return $this->getTypeFloat($val); + case self::TYPE_INT: + return $this->getTypeInt($val); + case self::TYPE_OBJECT: + return $this->getTypeObject($val); + case self::TYPE_RESOURCE: + return array(self::TYPE_RESOURCE, self::TYPE_RAW); + case self::TYPE_STRING: + return $this->abstracter->abstractString->getType($val); + case self::TYPE_UNKNOWN: + return $this->getTypeUnknown($val); + default: + return array($type, null); + } + } + + /** + * Does value appear to be a unix timestamp? + * + * @param int|string|float $val Value to test + * + * @return bool + */ + public function isTimestamp($val) + { + $secs = 86400 * 90; // 90 days worth o seconds + $tsNow = \time(); + return \is_numeric($val) && $val > $tsNow - $secs && $val < $tsNow + $secs; + } + + /** + * Get Array's type & typeMore + * + * @param array $val array value + * + * @return array + */ + private function getTypeArray($val) + { + $type = self::TYPE_ARRAY; + $typeMore = self::TYPE_RAW; // needs abstracted (references removed / values abstracted if necessary) + if (\count($val) === 2 && $this->abstracter->debug->php->isCallable($val, Php::IS_CALLABLE_ARRAY_ONLY)) { + $type = self::TYPE_CALLABLE; + } + return array($type, $typeMore); + } + + /** + * Get Float's type & typeMore + * + * INF and NAN are considered "float" + * + * @param float $val float/INF/NAN + * + * @return array + */ + private function getTypeFloat($val) + { + $typeMore = null; + if ($val === INF) { + $typeMore = self::TYPE_FLOAT_INF; + } elseif (\is_nan($val)) { + // using is_nan() func as comparing with NAN constant doesn't work + $typeMore = self::TYPE_FLOAT_NAN; + } elseif ($this->isTimestamp($val)) { + $typeMore = self::TYPE_TIMESTAMP; + } + return array(self::TYPE_FLOAT, $typeMore); + } + + /** + * Get Int's type & typeMore + * + * INF and NAN are considered "float" + * + * @param float $val float/INF/NAN + * + * @return array + */ + private function getTypeInt($val) + { + $typeMore = $this->isTimestamp($val) + ? self::TYPE_TIMESTAMP + : null; + return array(self::TYPE_INT, $typeMore); + } + + /** + * Get Object's type & typeMore + * + * @param object $object any object + * + * @return array type & typeMore + */ + private function getTypeObject($object) + { + $type = self::TYPE_OBJECT; + $typeMore = self::TYPE_RAW; // needs abstracted + if ($object instanceof Abstraction) { + $type = $object['type']; + $typeMore = self::TYPE_ABSTRACTION; + } + return array($type, $typeMore); + } + + /** + * Get PHP type + * + * @param mixed` $val value + * + * @return string "array", "bool", "float", "int", "null", "object", "resource", "string", "unknown" + */ + private static function getTypePhp($val) + { + $type = \gettype($val); + $map = array( + 'boolean' => self::TYPE_BOOL, + 'double' => self::TYPE_FLOAT, + 'integer' => self::TYPE_INT, + 'NULL' => self::TYPE_NULL, + 'resource (closed)' => self::TYPE_RESOURCE, + 'unknown type' => self::TYPE_UNKNOWN, // closed resource (php < 7.2) + ); + if (isset($map[$type])) { + $type = $map[$type]; + } + return $type; + } + + /** + * Get "unknown" type & typeMore + * + * @param mixed $val value of unknown type (likely closed resource) + * + * @return array type and typeMore + * + * @SuppressWarnings(PHPMD.DevelopmentCodeFragment) + */ + private function getTypeUnknown($val) + { + $type = self::TYPE_UNKNOWN; + $typeMore = null; + /* + closed resource? + is_resource() returns false for a closed resource + gettype returns 'unknown type' or 'resource (closed)' + */ + if (\strpos(\print_r($val, true), 'Resource') === 0) { + $type = self::TYPE_RESOURCE; + $typeMore = self::TYPE_RAW; // needs abstracted + } + return array($type, $typeMore); + } +} diff --git a/src/Debug/Collector/AbstractAsyncMiddleware.php b/src/Debug/Collector/AbstractAsyncMiddleware.php index eaf3f8d4..99a69f64 100644 --- a/src/Debug/Collector/AbstractAsyncMiddleware.php +++ b/src/Debug/Collector/AbstractAsyncMiddleware.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -250,6 +250,13 @@ protected function logRequest(RequestInterface $request, array $requestInfo) } } + /** + * Log the request body + * + * @param RequestInterface $request Request + * + * @return void + */ protected function logRequestBody(RequestInterface $request) { if ($this->cfg['inclRequestBody'] === false) { diff --git a/src/Debug/Collector/MonologHandlerCompatTrait.php b/src/Debug/Collector/MonologHandlerCompatTrait.php index 67c35791..0ea76afa 100644 --- a/src/Debug/Collector/MonologHandlerCompatTrait.php +++ b/src/Debug/Collector/MonologHandlerCompatTrait.php @@ -1,4 +1,4 @@ -= 80200) { require __DIR__ . '/ExecuteQueryTrait_php8.2.php'; } else { + /** + * @phpcs:disable Generic.Classes.DuplicateClassName.Found + */ trait ExecuteQueryTrait { } diff --git a/src/Debug/Collector/MySqli/ExecuteQueryTrait_php8.2.php b/src/Debug/Collector/MySqli/ExecuteQueryTrait_php8.2.php index 9576e476..811bafb6 100644 --- a/src/Debug/Collector/MySqli/ExecuteQueryTrait_php8.2.php +++ b/src/Debug/Collector/MySqli/ExecuteQueryTrait_php8.2.php @@ -1,5 +1,15 @@ + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.1 + */ + namespace bdk\Debug\Collector\MySqli; use mysqli_result; @@ -7,6 +17,7 @@ /** * Define HP 8.2's mysqli::execute_query method * + * @phpcs:disable Generic.Classes.DuplicateClassName.Found * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps */ trait ExecuteQueryTrait @@ -14,7 +25,7 @@ trait ExecuteQueryTrait /** * {@inheritDoc} */ - public function execute_query(string $query, ?array $params = null): mysqli_result|bool + public function execute_query(string $query, ?array $params = null): mysqli_result|bool // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter { return $this->profileCall('execute_query', $query, \func_get_args()); } diff --git a/src/Debug/Collector/Pdo/MethodSignatureCompatTrait.php b/src/Debug/Collector/Pdo/MethodSignatureCompatTrait.php index 3914901b..fdc2af59 100644 --- a/src/Debug/Collector/Pdo/MethodSignatureCompatTrait.php +++ b/src/Debug/Collector/Pdo/MethodSignatureCompatTrait.php @@ -1,13 +1,8 @@ -= 50600) { require __DIR__ . '/MethodSignatureCompatTrait_php5.6.php'; } else { + /** + * @phpcs:disable Generic.Classes.DuplicateClassName.Found + */ trait MethodSignatureCompatTrait { /** diff --git a/src/Debug/Collector/Pdo/MethodSignatureCompatTrait_php5.6.php b/src/Debug/Collector/Pdo/MethodSignatureCompatTrait_php5.6.php index 3c477fc3..5644352d 100644 --- a/src/Debug/Collector/Pdo/MethodSignatureCompatTrait_php5.6.php +++ b/src/Debug/Collector/Pdo/MethodSignatureCompatTrait_php5.6.php @@ -2,6 +2,9 @@ namespace bdk\Debug\Collector\Pdo; +/** + * @phpcs:disable Generic.Classes.DuplicateClassName.Found + */ trait MethodSignatureCompatTrait { /** diff --git a/src/Debug/Collector/PhpCurlClass.php b/src/Debug/Collector/PhpCurlClass.php index 6595c4ce..c17569d9 100644 --- a/src/Debug/Collector/PhpCurlClass.php +++ b/src/Debug/Collector/PhpCurlClass.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -84,11 +84,11 @@ public function __construct($options = array(), Debug $debug = null) /** * {@inheritDoc} * - * @param resource $ch Curl handle + * @param resource $handle Curl handle * * @return mixed Returns the value provided by parseResponse. */ - public function exec($ch = null) + public function exec($handle = null) { $options = $this->buildOptionsDebug(); $this->debug->groupCollapsed( @@ -102,7 +102,21 @@ public function exec($ch = null) ); $this->debug->time($this->debugOptions['label']); $this->debug->log('options', $options); - $return = parent::exec($ch); + $return = parent::exec($handle); + $this->execLog($options); + $this->debug->groupEnd(); + return $return; + } + + /** + * Log request and response + * + * @param array $options [description] + * + * @return void + */ + private function execLog(array $options) + { $verboseOutput = null; $this->rawRequestHeaders = $this->getInfo(CURLINFO_HEADER_OUT); if (!empty($options['CURLOPT_VERBOSE'])) { @@ -119,8 +133,6 @@ public function exec($ch = null) $this->requestHeaders = $this->reflection['parseReqHeaders']->invoke($this, $this->rawRequestHeaders); } $this->logRequestResponse($verboseOutput, $options); - $this->debug->groupEnd(); - return $return; } /** diff --git a/src/Debug/Collector/SoapClient.php b/src/Debug/Collector/SoapClient.php index 74aaf771..22a7ca18 100644 --- a/src/Debug/Collector/SoapClient.php +++ b/src/Debug/Collector/SoapClient.php @@ -6,15 +6,15 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Collector; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use Exception; use SoapClient as SoapClientBase; use SoapFault; @@ -334,7 +334,7 @@ private function logXml($label, $xml) { $this->debug->log( $label, - new Abstraction(Abstracter::TYPE_STRING, array( + new Abstraction(Type::TYPE_STRING, array( 'addQuotes' => false, 'attribs' => array( 'class' => 'highlight language-xml', diff --git a/src/Debug/Collector/StatementInfo.php b/src/Debug/Collector/StatementInfo.php index 683c4561..210196cb 100644 --- a/src/Debug/Collector/StatementInfo.php +++ b/src/Debug/Collector/StatementInfo.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -14,8 +14,8 @@ use bdk\Debug; use bdk\Debug\AbstractComponent; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\HttpMessage\Utility\ContentType; use Closure; use Exception; @@ -349,7 +349,7 @@ protected function logParams() $type = $this->types[$name]; $params[$name]['type'] = $type; // integer value if (isset(self::$constants[$type])) { - $params[$name]['type'] = new Abstraction(Abstracter::TYPE_CONST, array( + $params[$name]['type'] = new Abstraction(Type::TYPE_CONST, array( 'name' => self::$constants[$type], 'value' => $type, )); diff --git a/src/Debug/Collector/SwiftMailerLogger.php b/src/Debug/Collector/SwiftMailerLogger.php index b480a34b..28ab013b 100644 --- a/src/Debug/Collector/SwiftMailerLogger.php +++ b/src/Debug/Collector/SwiftMailerLogger.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -92,8 +92,6 @@ public function beforeSendPerformed(Swift_Events_SendEvent $event) * @param Swift_Events_SendEvent $event Swift SendEvent Instance * * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function sendPerformed(Swift_Events_SendEvent $event) { diff --git a/src/Debug/Config.php b/src/Debug/Config.php index 5dd05214..ea30b7f6 100644 --- a/src/Debug/Config.php +++ b/src/Debug/Config.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -276,9 +276,12 @@ private function getPropCfg($debugProp, $path = array(), $forInit = false, $delP unset($this->valuesPending[$debugProp]); } } - return $val !== null - ? $val - : $this->getPropCfgFromObj($debugProp, $path, $forInit); + if ($val !== null) { + return $val; + } + return $debugProp === 'debug' + ? $this->debug->getCfg($path, Debug::CONFIG_DEBUG) + : $this->getPropCfgFromChildObj($debugProp, $path, $forInit); } /** @@ -290,13 +293,10 @@ private function getPropCfg($debugProp, $path = array(), $forInit = false, $delP * * @return mixed */ - private function getPropCfgFromObj($debugProp, $path = array(), $forInit = false) + private function getPropCfgFromChildObj($debugProp, $path = array(), $forInit = false) { $obj = null; $matches = array(); - if ($debugProp === 'debug') { - return $this->debug->getCfg($path, Debug::CONFIG_DEBUG); - } if (\in_array($debugProp, $this->invokedServices, true)) { $obj = $this->debug->{$debugProp}; } elseif ($forInit) { @@ -343,9 +343,7 @@ private function normalizeArray(array $cfg) $return = array(); foreach ($cfg as $path => $v) { $ref = &$return; - $path = isset($this->configKeys[$path]) - ? array($path) - : $this->normalizePath($path); + $path = $this->normalizePath($path); foreach ($path as $k) { if (!isset($ref[$k])) { $ref[$k] = array(); diff --git a/src/Debug/Debug.php b/src/Debug/Debug.php index 6130731a..6b276c14 100644 --- a/src/Debug/Debug.php +++ b/src/Debug/Debug.php @@ -6,8 +6,8 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent - * @version v3.1 + * @copyright 2014-2024 Brad Kent + * @version v3.3 * * @link http://www.github.com/bkdotcom/PHPDebugConsole * @link https://developer.mozilla.org/en-US/docs/Web/API/console @@ -111,7 +111,7 @@ class Debug extends AbstractDebug const EVENT_STREAM_WRAP = 'debug.streamWrap'; const META = "\x00meta\x00"; - const VERSION = '3.2'; + const VERSION = '3.3'; protected $cfg = array( 'channelIcon' => 'fa fa-list-ul', @@ -320,11 +320,11 @@ public static function getInstance($cfg = array()) * * 'key', value * * 'key' (value defaults to true) * - * @param mixed $args,... arguments + * @param mixed ...$arg arguments * * @return array special array storing "meta" values */ - public static function meta() + public static function meta($arg = null) { $args = \func_get_args(); /** @var mixed[] make psalm happy */ diff --git a/src/Debug/Dump/Base.php b/src/Debug/Dump/Base.php index a308db55..9bf7035f 100644 --- a/src/Debug/Dump/Base.php +++ b/src/Debug/Dump/Base.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -15,6 +15,7 @@ use bdk\Debug; use bdk\Debug\AbstractComponent; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; /** @@ -96,8 +97,8 @@ public function processLogEntry(LogEntry $logEntry) */ public function substitutionAsString($val, $opts) { - list($type, $typeMore) = $this->debug->abstracter->getType($val); - if ($type === Abstracter::TYPE_ARRAY) { + list($type, $typeMore) = $this->debug->abstracter->type->getType($val); + if ($type === Type::TYPE_ARRAY) { $count = \count($val); if ($count) { // replace with dummy array so browser console will display native Array(length) @@ -105,7 +106,7 @@ public function substitutionAsString($val, $opts) } return $val; } - if ($type === Abstracter::TYPE_OBJECT) { + if ($type === Type::TYPE_OBJECT) { return (string) $val; // __toString or className } $opts['type'] = $type; diff --git a/src/Debug/Dump/BaseValue.php b/src/Debug/Dump/BaseValue.php index 6397722c..6a246835 100644 --- a/src/Debug/Dump/BaseValue.php +++ b/src/Debug/Dump/BaseValue.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -16,6 +16,7 @@ use bdk\Debug\AbstractComponent; use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Dump\Base as Dumper; use bdk\PubSub\Event; use DateTime; @@ -32,12 +33,12 @@ class BaseValue extends AbstractComponent protected $dumpOptions = array(); protected $dumpOptStack = array(); protected $simpleTypes = array( - Abstracter::TYPE_ARRAY, - Abstracter::TYPE_BOOL, - Abstracter::TYPE_FLOAT, - Abstracter::TYPE_INT, - Abstracter::TYPE_NULL, - Abstracter::TYPE_STRING, + Type::TYPE_ARRAY, + Type::TYPE_BOOL, + Type::TYPE_FLOAT, + Type::TYPE_INT, + Type::TYPE_NULL, + Type::TYPE_STRING, ); /** @@ -69,17 +70,17 @@ public function dump($val, $opts = array()) 'visualWhiteSpace' => true, ), $opts); if ($opts['type'] === null) { - list($opts['type'], $opts['typeMore']) = $this->debug->abstracter->getType($val); + list($opts['type'], $opts['typeMore']) = $this->debug->abstracter->type->getType($val); } - if ($opts['typeMore'] === Abstracter::TYPE_RAW) { - if ($opts['type'] === Abstracter::TYPE_OBJECT || $this->dumper->crateRaw) { + if ($opts['typeMore'] === Type::TYPE_RAW) { + if ($opts['type'] === Type::TYPE_OBJECT || $this->dumper->crateRaw) { $val = $this->debug->abstracter->crate($val, 'dump'); } $opts['typeMore'] = null; } $this->dumpOptStack[] = $opts; $method = 'dump' . \ucfirst($opts['type']); - $return = $opts['typeMore'] === Abstracter::TYPE_ABSTRACTION + $return = $opts['typeMore'] === Type::TYPE_ABSTRACTION ? $this->dumpAbstraction($val) : $this->{$method}($val); $this->dumpOptions = \array_pop($this->dumpOptStack); @@ -150,7 +151,7 @@ public function markupIdentifier($val) */ protected function checkTimestamp($val, Abstraction $abs = null) { - if ($abs && $abs['typeMore'] === Abstracter::TYPE_TIMESTAMP) { + if ($abs && $abs['typeMore'] === Type::TYPE_TIMESTAMP) { $datetime = new DateTime('@' . (int) $val); $datetimeStr = $datetime->format('Y-m-d H:i:s T'); $datetimeStr = \str_replace('GMT+0000', 'GMT', $datetimeStr); @@ -338,12 +339,13 @@ protected function dumpObjectProperties(Abstraction $abs) $return = array(); $properties = $abs->sort($abs['properties'], $abs['sort']); foreach ($properties as $name => $info) { - $info['isInherited'] = $info['declaredLast'] && $info['declaredLast'] !== $abs['className']; - $vis = $this->dumpPropVis($info); + $name = \str_replace('debug.', '', $name); $name = $this->dumpKeys ? $this->dump($name, array('addQuotes' => false)) : $name; - $name = '(' . $vis . ') ' . \str_replace('debug.', '', $name); + $info['isInherited'] = $info['declaredLast'] && $info['declaredLast'] !== $abs['className']; + $vis = $this->dumpPropVis($info); + $name = '(' . $vis . ') ' . $name; $return[$name] = $this->dump($info['value']); } return $return; @@ -504,7 +506,7 @@ private function dumpAbstractionOpts(Abstraction $abs) */ private function dumpStringAbs(Abstraction $abs) { - if ($abs['typeMore'] === Abstracter::TYPE_STRING_BINARY && !$abs['value']) { + if ($abs['typeMore'] === Type::TYPE_STRING_BINARY && !$abs['value']) { return 'Binary data not collected'; } $val = $this->debug->utf8->dump($abs['value']); diff --git a/src/Debug/Dump/Html.php b/src/Debug/Dump/Html.php index 1a2a7cf0..8796d07c 100644 --- a/src/Debug/Dump/Html.php +++ b/src/Debug/Dump/Html.php @@ -6,14 +6,15 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Dump; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; +use bdk\Debug\Dump\Html\Group; use bdk\Debug\Dump\Html\Helper; use bdk\Debug\Dump\Html\Table; use bdk\Debug\Dump\Html\Value; @@ -32,8 +33,8 @@ class Html extends Base /** @var Debug[] Logged channels (channelName => Debug) */ protected $channels = array(); - /** @var array LogEntry meta attribs */ - protected $logEntryAttribs = array(); + /** @var Group */ + protected $group; /** @var \bdk\Debug\Utility\Html */ protected $html; @@ -41,6 +42,9 @@ class Html extends Base /** @var HtmlTable */ protected $lazyTable; + /** @var array LogEntry meta attribs */ + protected $logEntryAttribs = array(); + /** * Constructor * @@ -49,6 +53,7 @@ class Html extends Base public function __construct(Debug $debug) { parent::__construct($debug); + $this->group = new Group($this); $this->helper = new Helper($this); $this->html = $debug->html; } @@ -94,17 +99,16 @@ public function processLogEntry(LogEntry $logEntry) */ public function substitutionAsString($val, $opts) { - // function array dereferencing = php 5.4 - $type = $this->debug->abstracter->getType($val)[0]; - if ($type === Abstracter::TYPE_STRING) { + $type = $this->debug->abstracter->type->getType($val)[0]; + if ($type === Type::TYPE_STRING) { return $this->valDumper->string->dumpAsSubstitution($val, $opts); } - if ($type === Abstracter::TYPE_ARRAY) { + if ($type === Type::TYPE_ARRAY) { $count = \count($val); return 'array' . '(' . $count . ')'; } - if ($type === Abstracter::TYPE_OBJECT) { + if ($type === Type::TYPE_OBJECT) { $opts['tagName'] = null; $toStr = (string) $val; // objects __toString or its classname return $toStr === $val['className'] @@ -255,98 +259,7 @@ protected function methodDefault(LogEntry $logEntry) */ protected function methodGroup(LogEntry $logEntry) { - $method = $logEntry['method']; - if ($method === 'groupEnd') { - return '' . "\n" . ''; - } - $meta = $this->methodGroupPrep($logEntry); - - $str = 'html->buildAttribString($this->logEntryAttribs) . '>' . "\n"; - $str .= $this->html->buildTag( - 'div', - array( - 'class' => 'group-header', - ), - $this->methodGroupHeader($logEntry['args'], $meta) - ) . "\n"; - $str .= 'html->buildAttribString(array( - 'class' => 'group-body', - )) . '>'; - return $str; - } - - /** - * Adds 'class' value to `$this->logEntryAttribs` - * - * @param LogEntry $logEntry LogEntry instance - * - * @return array meta values - */ - private function methodGroupPrep(LogEntry $logEntry) - { - $meta = \array_merge(array( - 'argsAsParams' => true, - 'boldLabel' => true, - 'hideIfEmpty' => false, - 'isFuncName' => false, - 'level' => null, - ), $logEntry['meta']); - - $classes = (array) $this->logEntryAttribs['class']; - if ($logEntry['method'] === 'group') { - // groupCollapsed doesn't get expanded - $classes[] = 'expanded'; - } - if ($meta['hideIfEmpty']) { - $classes[] = 'hide-if-empty'; - } - if ($meta['level']) { - $classes[] = 'level-' . $meta['level']; - } - $classes = \implode(' ', $classes); - $classes = \str_replace('m_' . $logEntry['method'], 'm_group', $classes); - $this->logEntryAttribs['class'] = $classes; - return $meta; - } - - /** - * Build group header - * - * @param array $args arguments - * @param array $meta meta values - * - * @return string - */ - private function methodGroupHeader($args, $meta) - { - $label = \array_shift($args); - $label = $meta['isFuncName'] - ? $this->valDumper->markupIdentifier($label, true) - : \preg_replace('#^(.+)$#s', '$1', $this->valDumper->dump($label)); - $labelClasses = \implode(' ', \array_keys(\array_filter(array( - 'font-weight-bold' => $meta['boldLabel'], - 'group-label' => true, - )))); - - $headerAppend = ''; - - if ($args) { - foreach ($args as $k => $v) { - $args[$k] = $this->valDumper->dump($v); - } - $argStr = \implode(', ', $args); - $label .= $meta['argsAsParams'] - ? '(' . $argStr . ')' - : ':'; - $headerAppend = $meta['argsAsParams'] - ? '' - : ' ' . $argStr; - } - - return '' - . $label - . '' - . $headerAppend; + return $this->group->build($logEntry, $this->logEntryAttribs); } /** diff --git a/src/Debug/Dump/Html/AbstractObjectSection.php b/src/Debug/Dump/Html/AbstractObjectSection.php index fafc9509..b070619e 100644 --- a/src/Debug/Dump/Html/AbstractObjectSection.php +++ b/src/Debug/Dump/Html/AbstractObjectSection.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -53,67 +53,15 @@ public function __construct(ValDumper $valDumper, Helper $helper, HtmlUtil $html */ public function dumpItems(ObjectAbstraction $abs, $what, array $cfg) { - $items = $abs->sort($abs[$what], $abs['sort']); $cfg = \array_merge(array( 'groupByInheritance' => \strpos($abs['sort'], 'inheritance') === 0, 'objClassName' => $abs['className'], 'phpDocOutput' => $abs['cfgFlags'] & AbstractObject::PHPDOC_OUTPUT, + 'what' => $what, ), $cfg); - if ($cfg['groupByInheritance'] === false) { - return $this->dumpItemsFiltered($items, $cfg); - } - // group by inheritance... with headings - // stop looping over classes when we've output everything - // no sense in showing "inherited from" when no more inherited items - // Or, we could only display the heading when itemsFiltered non-empty - $classes = $this->getInheritedClasses($abs, $what); - \array_unshift($classes, $abs['className']); - $html = ''; - $itemCount = \count($items); - $itemOutCount = 0; - while ($classes && $itemOutCount < $itemCount) { - $classname = \array_shift($classes); - $itemsFiltered = \array_filter($items, static function ($info) use ($classname) { - return !isset($info['declaredLast']) || $info['declaredLast'] === $classname; - }); - $items = \array_diff_key($items, $itemsFiltered); - $itemOutCount += \count($itemsFiltered); - $html .= \in_array($classname, array($abs['className'], 'stdClass'), true) === false - ? '
Inherited from ' . $this->valDumper->markupIdentifier($classname) . '
' . "\n" - : ''; - $html .= $this->dumpItemsFiltered($itemsFiltered, $cfg); - } - return $html; - } - - /** - * Get the extended classes we'll iterate over for "groupByInheritance" - * - * @param ObjectAbstraction $abs Object abstraction - * @param string $what 'cases', 'constants', 'properties', or 'methods' - * - * @return array - */ - private function getInheritedClasses(ObjectAbstraction $abs, $what) - { - $classes = $abs['extends']; - if ($what !== 'constants') { - return $classes; - } - // constants can be defined in interface - $implements = $abs['implements']; - $implementsList = array(); - while ($implements) { - $key = \key($implements); - $val = \array_shift($implements); - if (\is_array($val)) { - $implementsList[] = $key; - \array_splice($implements, 0, 0, $val); - continue; - } - $implementsList[] = $val; - } - return \array_merge($classes, $implementsList); + return $cfg['groupByInheritance'] + ? $this->dumpItemsByInheritance($abs, $cfg) + : $this->dumpItemsFiltered($abs->sort($abs[$cfg['what']], $abs['sort']), $cfg); } /** @@ -172,7 +120,7 @@ protected function dumpItemInner($name, array $info, array $cfg) * Iterate over cases, constants, properties, or methods * * @param array $items Cases, Constants, Properties, or Methods - * @param array $cfg config options + * @param array $cfg Config options * * @return string */ @@ -196,6 +144,41 @@ private function dumpItemsFiltered(array $items, array $cfg) return $html; } + /** + * group by inheritance... with headings + * + * @param ObjectAbstraction $abs ObjectAbstraction instance + * @param array $cfg Config options + * + * @return string + */ + private function dumpItemsByInheritance(ObjectAbstraction $abs, array $cfg) + { + $html = ''; + $className = $abs['className']; + $classes = $this->getInheritedClasses($abs, $abs['what']); + \array_unshift($classes, $className); + $items = $abs->sort($abs[$cfg['what']], $abs['sort']); + $itemCount = \count($items); + $itemOutCount = 0; + // stop looping over classes when we've output everything + // no sense in showing "inherited from" when no more inherited items + // Or, we could only display the heading when itemsFiltered non-empty + while ($classes && $itemOutCount < $itemCount) { + $classNameCur = \array_shift($classes); + $itemsFiltered = \array_filter($items, static function ($info) use ($classNameCur) { + return !isset($info['declaredLast']) || $info['declaredLast'] === $classNameCur; + }); + $items = \array_diff_key($items, $itemsFiltered); + $itemOutCount += \count($itemsFiltered); + $html .= \in_array($classNameCur, array($className, 'stdClass'), true) === false + ? '
Inherited from ' . $this->valDumper->markupIdentifier($classNameCur) . '
' . "\n" + : ''; + $html .= $this->dumpItemsFiltered($itemsFiltered, $cfg); + } + return $html; + } + /** * Dump "modifiers" * @@ -245,6 +228,36 @@ protected function getAttribs(array $info, array $cfg = array()) */ abstract protected function getClasses(array $info); + /** + * Get the extended classes we'll iterate over for "groupByInheritance" + * + * @param ObjectAbstraction $abs Object abstraction + * @param string $what 'cases', 'constants', 'properties', or 'methods' + * + * @return array + */ + private function getInheritedClasses(ObjectAbstraction $abs, $what) + { + $classes = $abs['extends']; + if ($what !== 'constants') { + return $classes; + } + // constants can be defined in interface + $implements = $abs['implements']; + $implementsList = array(); + while ($implements) { + $key = \key($implements); + $val = \array_shift($implements); + if (\is_array($val)) { + $implementsList[] = $key; + \array_splice($implements, 0, 0, $val); + continue; + } + $implementsList[] = $val; + } + return \array_merge($classes, $implementsList); + } + /** * Get "modifiers" (final, readonly, static) * diff --git a/src/Debug/Dump/Html/Group.php b/src/Debug/Dump/Html/Group.php new file mode 100644 index 00000000..cdf9c4d3 --- /dev/null +++ b/src/Debug/Dump/Html/Group.php @@ -0,0 +1,143 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\Debug\Dump\Html; + +use bdk\Debug\Dump\Html as Dumper; +use bdk\Debug\LogEntry; + +/** + * Build output for group, groupCollapsed, & groupEnd + */ +class Group +{ + /** @var Dumper */ + protected $dumper; + + /** @var \bdk\Debug\Utility\Html */ + protected $html; + + /** @var array LogEntry meta attribs */ + protected $logEntryAttribs = array(); + + /** + * Constructor + * + * @param Dumper $dumper html dumper + */ + public function __construct(Dumper $dumper) + { + $this->dumper = $dumper; + $this->html = $dumper->debug->html; + } + + /** + * Build output for group, groupCollapsed, & groupEnd + * + * @param LogEntry $logEntry LogEntry instance + * @param array $logEntryAttribs tag attributes + * + * @return string + */ + public function build(LogEntry $logEntry, array $logEntryAttribs) + { + $this->logEntryAttribs = $logEntryAttribs; + $method = $logEntry['method']; + if ($method === 'groupEnd') { + return '' . "\n" . ''; + } + $meta = $this->prep($logEntry); + + $str = 'html->buildAttribString($this->logEntryAttribs) . '>' . "\n"; + $str .= $this->html->buildTag( + 'div', + array( + 'class' => 'group-header', + ), + $this->header($logEntry['args'], $meta) + ) . "\n"; + $str .= 'html->buildAttribString(array( + 'class' => 'group-body', + )) . '>'; + return $str; + } + + /** + * Adds 'class' value to `$this->logEntryAttribs` + * + * @param LogEntry $logEntry LogEntry instance + * + * @return array meta values + */ + private function prep(LogEntry $logEntry) + { + $meta = \array_merge(array( + 'argsAsParams' => true, + 'boldLabel' => true, + 'hideIfEmpty' => false, + 'isFuncName' => false, + 'level' => null, + ), $logEntry['meta']); + + $classes = (array) $this->logEntryAttribs['class']; + if ($logEntry['method'] === 'group') { + // groupCollapsed doesn't get expanded + $classes[] = 'expanded'; + } + if ($meta['hideIfEmpty']) { + $classes[] = 'hide-if-empty'; + } + if ($meta['level']) { + $classes[] = 'level-' . $meta['level']; + } + $classes = \implode(' ', $classes); + $classes = \str_replace('m_' . $logEntry['method'], 'm_group', $classes); + $this->logEntryAttribs['class'] = $classes; + return $meta; + } + + /** + * Build group header + * + * @param array $args arguments + * @param array $meta meta values + * + * @return string + */ + private function header(array $args, array $meta) + { + $label = \array_shift($args); + $label = $meta['isFuncName'] + ? $this->dumper->valDumper->markupIdentifier($label, true) + : \preg_replace('#^(.+)$#s', '$1', $this->dumper->valDumper->dump($label)); + + $labelClasses = \implode(' ', \array_keys(\array_filter(array( + 'font-weight-bold' => $meta['boldLabel'], + 'group-label' => true, + )))); + + if (!$args) { + return '' . $label . ''; + } + + foreach ($args as $k => $v) { + $args[$k] = $this->dumper->valDumper->dump($v); + } + $argStr = \implode(', ', $args); + return $meta['argsAsParams'] + ? '' . $label . '(' + . $argStr + . ')' + : '' . $label . ': ' + . $argStr; + } +} diff --git a/src/Debug/Dump/Html/Helper.php b/src/Debug/Dump/Html/Helper.php index 42d7366b..1af50218 100644 --- a/src/Debug/Dump/Html/Helper.php +++ b/src/Debug/Dump/Html/Helper.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -14,6 +14,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Dump\Html as Dumper; /** @@ -182,12 +183,12 @@ public function tableMarkupFunction($html, array $row) private function buildArgStringArgs(array $args, array $meta) { foreach ($args as $i => $v) { - list($type, $typeMore) = $this->debug->abstracter->getType($v); - $typeMore2 = $typeMore === Abstracter::TYPE_ABSTRACTION + list($type, $typeMore) = $this->debug->abstracter->type->getType($v); + $typeMore2 = $typeMore === Type::TYPE_ABSTRACTION ? $v['typeMore'] : $typeMore; - $isNumericString = $type === Abstracter::TYPE_STRING - && \in_array($typeMore2, array(Abstracter::TYPE_STRING_NUMERIC, Abstracter::TYPE_TIMESTAMP), true); + $isNumericString = $type === Type::TYPE_STRING + && \in_array($typeMore2, array(Type::TYPE_STRING_NUMERIC, Type::TYPE_TIMESTAMP), true); $args[$i] = $this->dumper->valDumper->dump($v, array( 'addQuotes' => $i !== 0 || $isNumericString, 'sanitize' => $i === 0 @@ -228,7 +229,7 @@ private function markupTypePart($type) $type = \trim($type, '\'"'); return '' . $type . ''; } - if (\in_array($type, $this->debug->phpDoc->types, true) === false) { + if (\in_array($type, $this->debug->phpDoc->type->types, true) === false) { $type = $this->dumper->valDumper->markupIdentifier($type); } if ($arrayCount > 0) { diff --git a/src/Debug/Dump/Html/HtmlObject.php b/src/Debug/Dump/Html/HtmlObject.php index 0c70705b..e7fea74d 100644 --- a/src/Debug/Dump/Html/HtmlObject.php +++ b/src/Debug/Dump/Html/HtmlObject.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -15,7 +15,10 @@ use bdk\Debug\Abstraction\Abstraction; use bdk\Debug\Abstraction\AbstractObject; use bdk\Debug\Dump\Html\Helper; +use bdk\Debug\Dump\Html\ObjectCases; +use bdk\Debug\Dump\Html\ObjectConstants; use bdk\Debug\Dump\Html\ObjectMethods; +use bdk\Debug\Dump\Html\ObjectPhpDoc; use bdk\Debug\Dump\Html\ObjectProperties; use bdk\Debug\Dump\Html\Value as ValDumper; use bdk\Debug\Utility\Html as HtmlUtil; @@ -31,6 +34,7 @@ class HtmlObject protected $helper; protected $html; protected $methods; + protected $phpDoc; protected $properties; protected $sectionCallables; @@ -49,6 +53,7 @@ public function __construct(ValDumper $valDumper, Helper $helper, HtmlUtil $html $this->cases = new ObjectCases($valDumper, $helper, $html); $this->constants = new ObjectConstants($valDumper, $helper, $html); $this->methods = new ObjectMethods($valDumper, $helper, $html); + $this->phpDoc = new ObjectPhpDoc($valDumper); $this->properties = new ObjectProperties($valDumper, $helper, $html); $this->sectionCallables = array( 'attributes' => array($this, 'dumpAttributes'), @@ -57,7 +62,7 @@ public function __construct(ValDumper $valDumper, Helper $helper, HtmlUtil $html 'extends' => array($this, 'dumpExtends'), 'implements' => array($this, 'dumpImplements'), 'methods' => array($this->methods, 'dump'), - 'phpDoc' => array($this, 'dumpPhpDoc'), + 'phpDoc' => array($this->phpDoc, 'dump'), 'properties' => array($this->properties, 'dump'), ); } @@ -277,7 +282,7 @@ protected function dumpImplements(Abstraction $abs) } /** - * Dump method modifiers (final) + * Dump modifiers (final & readonly) * * @param Abstraction $abs Object Abstraction instance * @@ -298,85 +303,6 @@ protected function dumpModifiers(Abstraction $abs) }, $modifiers)); } - /** - * Dump object's phpDoc info - * - * @param Abstraction $abs Object Abstraction instance - * - * @return string html fragment - */ - protected function dumpPhpDoc(Abstraction $abs) - { - $str = '
phpDoc
' . "\n"; - foreach ($abs['phpDoc'] as $tagName => $values) { - if (\is_array($values) === false) { - continue; - } - foreach ($values as $tagData) { - $tagData['tagName'] = $tagName; - $str .= $this->dumpPhpDocTag($tagData); - } - } - return $str; - } - - /** - * Markup tag - * - * @param array $tagData parsed tag - * - * @return string html fragment - */ - private function dumpPhpDocTag($tagData) - { - $tagName = $tagData['tagName']; - switch ($tagName) { - case 'author': - $info = $this->dumpPhpDocTagAuthor($tagData); - break; - case 'link': - case 'see': - $desc = $tagData['desc'] ?: $tagData['uri'] ?: ''; - if (isset($tagData['uri'])) { - $info = '' . \htmlspecialchars($desc) . ''; - break; - } - // see tag - $info = $this->valDumper->markupIdentifier($tagData['fqsen']) - . ' ' . \htmlspecialchars($desc) . ''; - $info = \str_replace(' ', '', $info); - break; - default: - unset($tagData['tagName']); - $info = \htmlspecialchars(\implode(' ', $tagData)); - } - return '
' - . '' . $tagName . '' - . ': ' - . $info - . '
' . "\n"; - } - - /** - * Dump PhpDoc author tag value - * - * @param array $tagData parsed tag - * - * @return string html partial - */ - private function dumpPhpDocTagAuthor($tagData) - { - $html = $tagData['name']; - if ($tagData['email']) { - $html .= ' <' . $tagData['email'] . '>'; - } - if ($tagData['desc']) { - // desc is non-standard for author tag - $html .= ' ' . \htmlspecialchars($tagData['desc']); - } - return $html; - } - /** * Dump object's __toString or stringified value * diff --git a/src/Debug/Dump/Html/HtmlString.php b/src/Debug/Dump/Html/HtmlString.php index 56f2d6c8..6f610476 100644 --- a/src/Debug/Dump/Html/HtmlString.php +++ b/src/Debug/Dump/Html/HtmlString.php @@ -6,14 +6,14 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Dump\Html; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Dump\Html\HtmlStringEncoded; use bdk\Debug\Dump\Html\Value as ValDumper; use RuntimeException; @@ -102,7 +102,7 @@ public function dump($val, Abstraction $abs = null) */ public function dumpAsSubstitution($val, $opts) { - $isBinary = $val instanceof Abstraction && $val['typeMore'] === Abstracter::TYPE_STRING_BINARY; + $isBinary = $val instanceof Abstraction && $val['typeMore'] === Type::TYPE_STRING_BINARY; if ($isBinary === false) { // we do NOT wrap in ... log('link', $url); $opts['tagName'] = null; @@ -132,9 +132,9 @@ public function dumpAsSubstitution($val, $opts) public function isEncoded($val) { $typesEncoded = array( - Abstracter::TYPE_STRING_BASE64, - Abstracter::TYPE_STRING_JSON, - Abstracter::TYPE_STRING_SERIALIZED, + Type::TYPE_STRING_BASE64, + Type::TYPE_STRING_JSON, + Type::TYPE_STRING_SERIALIZED, ); return $val instanceof Abstraction && \in_array($val['typeMore'], $typesEncoded, true); } @@ -167,14 +167,14 @@ public function visualWhiteSpace($str) */ private function dumpAbs(Abstraction $abs) { - if ($abs['typeMore'] === Abstracter::TYPE_STRING_CLASSNAME) { + if ($abs['typeMore'] === Type::TYPE_STRING_CLASSNAME) { return $this->dumpClassname($abs); } $val = $this->dumpHelper($abs['value']); if ($this->isEncoded($abs)) { return $this->dumpEncoded->dump($val, $abs); } - if ($abs['typeMore'] === Abstracter::TYPE_STRING_BINARY) { + if ($abs['typeMore'] === Type::TYPE_STRING_BINARY) { return $this->dumpBinary($val, $abs); } if ($abs['strlen']) { diff --git a/src/Debug/Dump/Html/HtmlStringEncoded.php b/src/Debug/Dump/Html/HtmlStringEncoded.php index 38b3d7fd..3f1c1f89 100644 --- a/src/Debug/Dump/Html/HtmlStringEncoded.php +++ b/src/Debug/Dump/Html/HtmlStringEncoded.php @@ -6,14 +6,14 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Dump\Html; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Dump\Html\HtmlString; /** @@ -157,7 +157,7 @@ private function tabValues(Abstraction $abs) $attribs = $this->valDumper->getDumpOpt('attribs'); $attribs['class'][] = 'no-quotes'; $attribs['class'][] = 't_' . $abs['type']; - if ($abs['typeMore'] === Abstracter::TYPE_STRING_BASE64 && $abs['brief']) { + if ($abs['typeMore'] === Type::TYPE_STRING_BASE64 && $abs['brief']) { $this->valDumper->setDumpOpt('postDump', static function ($dumped) { return 'string(base64): ' . $dumped; }); @@ -185,13 +185,13 @@ private function tabValues(Abstraction $abs) private function tabValuesFinish($vals, Abstraction $abs) { switch ($abs['typeMore']) { - case Abstracter::TYPE_STRING_BASE64: + case Type::TYPE_STRING_BASE64: $vals['labelRaw'] = 'base64'; if ($abs['strlen']) { $vals['valRaw'] .= '… ' . ($abs['strlen'] - \strlen($abs['value'])) . ' more bytes (not logged)'; } break; - case Abstracter::TYPE_STRING_JSON: + case Type::TYPE_STRING_JSON: $vals['labelRaw'] = 'json'; if ($abs['prettified'] || $abs['strlen']) { $abs['typeMore'] = null; // unset typeMore to prevent loop @@ -199,7 +199,7 @@ private function tabValuesFinish($vals, Abstraction $abs) $abs['typeMore'] = 'json'; } break; - case Abstracter::TYPE_STRING_SERIALIZED: + case Type::TYPE_STRING_SERIALIZED: $vals['labelDecoded'] = 'unserialized'; $vals['labelRaw'] = 'serialized'; break; diff --git a/src/Debug/Dump/Html/ObjectMethods.php b/src/Debug/Dump/Html/ObjectMethods.php index 59adb00e..abb87d10 100644 --- a/src/Debug/Dump/Html/ObjectMethods.php +++ b/src/Debug/Dump/Html/ObjectMethods.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -180,6 +180,12 @@ protected function dumpParamDefault($defaultValue) */ protected function dumpParamName(array $info) { + $name = \sprintf( + '%s%s$%s', + $info['isPassedByReference'] ? '&' : '', + $info['isVariadic'] ? '...' : '', + $info['name'] + ); return $this->html->buildTag( 'span', array( @@ -188,7 +194,7 @@ protected function dumpParamName(array $info) ? $info['desc'] : '', ), - \htmlspecialchars($info['name']) + \htmlspecialchars($name) ); } diff --git a/src/Debug/Dump/Html/ObjectPhpDoc.php b/src/Debug/Dump/Html/ObjectPhpDoc.php new file mode 100644 index 00000000..1164bb34 --- /dev/null +++ b/src/Debug/Dump/Html/ObjectPhpDoc.php @@ -0,0 +1,113 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\Debug\Dump\Html; + +use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Dump\Html\Value as ValDumper; + +/** + * Dump object properties as HTML + */ +class ObjectPhpDoc +{ + public $valDumper; + + /** + * Constructor + * + * @param ValDumper $valDumper Html dumper + */ + public function __construct(ValDumper $valDumper) + { + $this->valDumper = $valDumper; + } + + /** + * Dump object's phpDoc info + * + * @param Abstraction $abs Object Abstraction instance + * + * @return string html fragment + */ + public function dump(Abstraction $abs) + { + $str = '
phpDoc
' . "\n"; + foreach ($abs['phpDoc'] as $tagName => $values) { + if (\is_array($values) === false) { + continue; + } + foreach ($values as $tagData) { + $tagData['tagName'] = $tagName; + $str .= $this->dumpTag($tagData); + } + } + return $str; + } + + /** + * Markup tag + * + * @param array $tagData Parsed tag + * + * @return string html fragment + */ + private function dumpTag(array $tagData) + { + $tagName = $tagData['tagName']; + switch ($tagName) { + case 'author': + $info = $this->dumpTagAuthor($tagData); + break; + case 'link': + case 'see': + $desc = $tagData['desc'] ?: $tagData['uri'] ?: ''; + if (isset($tagData['uri'])) { + $info = '' . \htmlspecialchars($desc) . ''; + break; + } + // see tag + $info = $this->valDumper->markupIdentifier($tagData['fqsen']) + . ' ' . \htmlspecialchars($desc) . ''; + $info = \str_replace(' ', '', $info); + break; + default: + unset($tagData['tagName']); + $info = \htmlspecialchars(\implode(' ', $tagData)); + } + return '
' + . '' . $tagName . '' + . ': ' + . $info + . '
' . "\n"; + } + + /** + * Dump PhpDoc author tag value + * + * @param array $tagData parsed tag + * + * @return string html partial + */ + private function dumpTagAuthor(array $tagData) + { + $html = $tagData['name']; + if ($tagData['email']) { + $html .= ' <' . $tagData['email'] . '>'; + } + if ($tagData['desc']) { + // desc is non-standard for author tag + $html .= ' ' . \htmlspecialchars($tagData['desc']); + } + return $html; + } +} diff --git a/src/Debug/Dump/Html/Value.php b/src/Debug/Dump/Html/Value.php index f3cf7ebc..556f3b06 100644 --- a/src/Debug/Dump/Html/Value.php +++ b/src/Debug/Dump/Html/Value.php @@ -6,14 +6,14 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Dump\Html; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Dump\BaseValue; use bdk\Debug\Dump\Html as Dumper; use bdk\Debug\Dump\Html\HtmlObject; @@ -100,7 +100,7 @@ public function dump($val, $opts = array()) } $tagName = $this->dumpOptions['tagName']; if ($tagName === '__default__') { - $tagName = $this->dumpOptions['type'] === Abstracter::TYPE_OBJECT + $tagName = $this->dumpOptions['type'] === Type::TYPE_OBJECT ? 'div' : 'span'; } @@ -125,7 +125,7 @@ public function getDumpOpt($what = null) $val = parent::getDumpOpt($what); if ($what === 'tagName' && $val === '__default__') { $val = 'span'; - if (parent::getDumpOpt('type') === Abstracter::TYPE_OBJECT) { + if (parent::getDumpOpt('type') === Type::TYPE_OBJECT) { $val = 'div'; } } @@ -212,8 +212,7 @@ protected function dumpArray($array) . ' *MAX DEPTH*'; } if (empty($array)) { - return 'array' - . '()'; + return 'array()'; } if ($opts['expand'] !== null) { $this->setDumpOpt('attribs.data-expand', $opts['expand']); @@ -222,44 +221,42 @@ protected function dumpArray($array) $this->setDumpOpt('attribs.class.__push__', 'array-file-tree'); } $showKeys = $opts['showListKeys'] || !$this->debug->arrayUtil->isList($array); - $html = 'array' - . '(' . "\n" - . '
    ' . "\n"; - foreach ($array as $key => $val) { - $html .= $this->dumpArrayValue($key, $val, $showKeys); - } - $html .= '
' - . ')'; - return $html; + return 'array(' . "\n" + . '
    ' . "\n" + . $this->dumpArrayValues($array, $showKeys) + . '
)'; } /** * Dump an array key/value pair * - * @param int|string $key key - * @param mixed $val value - * @param bool $withKey include key with value? + * @param array $array array to output + * @param bool $outputKeys include key with value? * * @return string */ - private function dumpArrayValue($key, $val, $withKey) + private function dumpArrayValues(array $array, $outputKeys) { - return $withKey - ? "\t" . '
  • ' - . $this->html->buildTag( - 'span', - array( - 'class' => array( - 't_int' => \is_int($key), - 't_key' => true, + $html = ''; + foreach ($array as $key => $val) { + $html .= $outputKeys + ? "\t" . '
  • ' + . $this->html->buildTag( + 'span', + array( + 'class' => array( + 't_int' => \is_int($key), + 't_key' => true, + ), ), - ), - $this->dump($key, array('tagName' => null)) // don't wrap it - ) - . '=>' - . $this->dump($val) - . '
  • ' . "\n" - : "\t" . $this->dump($val, array('tagName' => 'li')) . "\n"; + $this->dump($key, array('tagName' => null)) // don't wrap it + ) + . '=>' + . $this->dump($val) + . '' . "\n" + : "\t" . $this->dump($val, array('tagName' => 'li')) . "\n"; + } + return $html; } /** @@ -314,10 +311,10 @@ protected function dumpConst(Abstraction $abs) */ protected function dumpFloat($val, Abstraction $abs = null) { - if ($val === Abstracter::TYPE_FLOAT_INF) { + if ($val === Type::TYPE_FLOAT_INF) { return 'INF'; } - if ($val === Abstracter::TYPE_FLOAT_NAN) { + if ($val === Type::TYPE_FLOAT_NAN) { return 'NaN'; } $this->checkTimestamp($val, $abs); @@ -359,7 +356,7 @@ protected function dumpObject(Abstraction $abs) */ protected function dumpRecursion() { - $this->setDumpOpt('type', Abstracter::TYPE_ARRAY); + $this->setDumpOpt('type', Type::TYPE_ARRAY); return 'array *RECURSION*'; } diff --git a/src/Debug/Dump/Substitution.php b/src/Debug/Dump/Substitution.php index faa8a4ee..cb374916 100644 --- a/src/Debug/Dump/Substitution.php +++ b/src/Debug/Dump/Substitution.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -68,6 +68,17 @@ public function process($args, $options = array()) ), $options), 'typeCounts' => \array_fill_keys(\str_split('coOdifs'), 0), ); + return $this->processArgs(); + } + + /** + * Update arguments + * + * @return array updated args + */ + private function processArgs() + { + $args = $this->subInfo['args']; $string = \preg_replace_callback($this->subRegex, array($this, 'processSubsCallback'), $args[0]); $args = $this->subInfo['args']; if (!$this->subInfo['options']['style']) { @@ -113,7 +124,7 @@ private function processSubsCallback($matches) } elseif (\preg_match('/[oO]/', $type)) { $replacement = $this->dumper->valDumper->dump($arg); } - $this->subInfo['typeCounts'][$type] ++; + $this->subInfo['typeCounts'][$type]++; if ($this->subInfo['options']['replace']) { unset($this->subInfo['args'][$index]); return $replacement; diff --git a/src/Debug/Dump/Text.php b/src/Debug/Dump/Text.php index a0af5f49..a82b7e3b 100644 --- a/src/Debug/Dump/Text.php +++ b/src/Debug/Dump/Text.php @@ -6,13 +6,13 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Dump; -use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Dump\TextValue; use bdk\Debug\LogEntry; @@ -76,13 +76,12 @@ public function processLogEntry(LogEntry $logEntry) */ public function substitutionAsString($val, $opts) { - // function array dereferencing = php 5.4 - $type = $this->debug->abstracter->getType($val)[0]; - if ($type === Abstracter::TYPE_ARRAY) { + $type = $this->debug->abstracter->type->getType($val)[0]; + if ($type === Type::TYPE_ARRAY) { $count = \count($val); return 'array(' . $count . ')'; } - if ($type === Abstracter::TYPE_OBJECT) { + if ($type === Type::TYPE_OBJECT) { return (string) $val; // __toString or className } return $this->valDumper->dump($val, $opts); @@ -98,12 +97,12 @@ public function substitutionAsString($val, $opts) protected function buildArgString($args) { foreach ($args as $i => $v) { - list($type, $typeMore) = $this->debug->abstracter->getType($v); - $typeMore2 = $typeMore === Abstracter::TYPE_ABSTRACTION + list($type, $typeMore) = $this->debug->abstracter->type->getType($v); + $typeMore2 = $typeMore === Type::TYPE_ABSTRACTION ? $v['typeMore'] : $typeMore; - $isNumericString = $type === Abstracter::TYPE_STRING - && \in_array($typeMore2, array(Abstracter::TYPE_STRING_NUMERIC, Abstracter::TYPE_TIMESTAMP), true); + $isNumericString = $type === Type::TYPE_STRING + && \in_array($typeMore2, array(Type::TYPE_STRING_NUMERIC, Type::TYPE_TIMESTAMP), true); $args[$i] = $this->valDumper->dump($v, array( 'addQuotes' => $i !== 0 || $isNumericString, 'type' => $type, @@ -220,7 +219,7 @@ protected function methodGroup(LogEntry $logEntry) return '======='; } if ($this->depth > 0) { - $this->depth --; + $this->depth--; } return ''; } diff --git a/src/Debug/Dump/TextAnsiValue.php b/src/Debug/Dump/TextAnsiValue.php index 1fcdf6a8..788c4ef6 100644 --- a/src/Debug/Dump/TextAnsiValue.php +++ b/src/Debug/Dump/TextAnsiValue.php @@ -6,15 +6,15 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Dump; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; use bdk\Debug\Abstraction\AbstractObject; +use bdk\Debug\Abstraction\Type; /** * Base output plugin @@ -115,9 +115,9 @@ protected function dumpBool($val) */ protected function dumpFloat($val, Abstraction $abs = null) { - if ($val === Abstracter::TYPE_FLOAT_INF) { + if ($val === Type::TYPE_FLOAT_INF) { $val = 'INF'; - } elseif ($val === Abstracter::TYPE_FLOAT_NAN) { + } elseif ($val === Type::TYPE_FLOAT_NAN) { $val = 'NaN'; } $date = $this->checkTimestamp($val, $abs); @@ -191,7 +191,7 @@ protected function dumpObjectMethods(Abstraction $abs) 'magic' => 0, ); foreach ($abs['methods'] as $info) { - $counts[ $info['visibility'] ] ++; + $counts[ $info['visibility'] ]++; } $counts = \array_filter($counts); $header = $counts @@ -226,18 +226,7 @@ protected function dumpObjectProperties(Abstraction $abs) foreach ($properties as $name => $info) { $info['className'] = $abs['className']; $info['isInherited'] = $info['declaredLast'] && $info['declaredLast'] !== $abs['className']; - $prefix = $this->dumpPropPrefix($info); - $vis = $this->cfg['escapeCodes']['muted'] . '(' . $this->dumpPropVis($info) . ')' . $this->escapeReset; - $name = $this->cfg['escapeCodes']['property'] . $name . $this->escapeReset; - $val = $info['debugInfoExcluded'] - ? '' - : \sprintf( - ' %s=%s %s', - $this->cfg['escapeCodes']['operator'], - $this->escapeReset, - $this->dump($info['value']) - ); - $str .= \sprintf(' %s%s %s%s', $prefix, $vis, $name, $val) . "\n"; + $str .= $this->dumpProp($name, $info); } $header = $str ? "\e[4mProperties:\e[24m" @@ -245,6 +234,30 @@ protected function dumpObjectProperties(Abstraction $abs) return ' ' . $header . "\n" . $str; } + /** + * Dump object property + * + * @param string $name Property name + * @param array $info Property info + * + * @return string + */ + protected function dumpProp($name, array $info) + { + $name = $this->cfg['escapeCodes']['property'] . $name . $this->escapeReset; + $prefix = $this->dumpPropPrefix($info); + $vis = $this->cfg['escapeCodes']['muted'] . '(' . $this->dumpPropVis($info) . ')' . $this->escapeReset; + $val = $info['debugInfoExcluded'] + ? '' + : \sprintf( + ' %s=%s %s', + $this->cfg['escapeCodes']['operator'], + $this->escapeReset, + $this->dump($info['value']) + ); + return \sprintf(' %s%s %s%s', $prefix, $vis, $name, $val) . "\n"; + } + /** * Get inherited/dynamic/override indicator * diff --git a/src/Debug/Dump/TextValue.php b/src/Debug/Dump/TextValue.php index 134c80ad..8d6eb17e 100644 --- a/src/Debug/Dump/TextValue.php +++ b/src/Debug/Dump/TextValue.php @@ -6,15 +6,15 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Dump; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; use bdk\Debug\Abstraction\AbstractObject; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Dump\BaseValue; /** @@ -81,10 +81,10 @@ protected function dumpBool($val) */ protected function dumpFloat($val, Abstraction $abs = null) { - if ($val === Abstracter::TYPE_FLOAT_INF) { + if ($val === Type::TYPE_FLOAT_INF) { return 'INF'; } - if ($val === Abstracter::TYPE_FLOAT_NAN) { + if ($val === Type::TYPE_FLOAT_NAN) { return 'NaN'; } $date = $this->checkTimestamp($val, $abs); @@ -156,7 +156,7 @@ protected function dumpObjectMethods(Abstraction $abs) 'magic' => 0, ); foreach ($abs['methods'] as $info) { - $counts[ $info['visibility'] ] ++; + $counts[ $info['visibility'] ]++; } foreach ($counts as $vis => $count) { if ($count > 0) { @@ -186,19 +186,9 @@ protected function dumpObjectProperties(Abstraction $abs) $properties = $abs->sort($abs['properties'], $abs['sort']); foreach ($properties as $name => $info) { $name = \str_replace('debug.', '', $name); - $name = $this->dumpKeys - ? $this->dump($name, array( - 'addQuotes' => \preg_match('#[\s\r\n]#u', $name) === 1 || $name === '', - )) - : $name; $info['className'] = $abs['className']; $info['isInherited'] = $info['declaredLast'] && $info['declaredLast'] !== $abs['className']; - $prefix = $this->dumpPropPrefix($info); - $vis = $this->dumpPropVis($info); - $val = $info['debugInfoExcluded'] - ? '' - : ' = ' . $this->dump($info['value']); - $str .= ' ' . $prefix . '(' . $vis . ') ' . $name . $val . "\n"; + $str .= $this->dumpProp($name, $info); } $propHeader = $str ? 'Properties:' @@ -206,6 +196,32 @@ protected function dumpObjectProperties(Abstraction $abs) return ' ' . $propHeader . "\n" . $str; } + /** + * Dump object property + * + * @param string $name Property name + * @param array $info Property info + * + * @return string + */ + protected function dumpProp($name, array $info) + { + $name = $this->dumpKeys + ? $this->dump($name, array( + 'addQuotes' => \preg_match('#[\s\r\n]#u', $name) === 1 || $name === '', + )) + : $name; + return \sprintf( + ' %s(%s) %s%s' . "\n", + $this->dumpPropPrefix($info), + $this->dumpPropVis($info), + $name, + $info['debugInfoExcluded'] + ? '' + : ' = ' . $this->dump($info['value']) + ) . "\n"; + } + /** * Get inherited/dynamic/override indicator * @@ -243,19 +259,16 @@ protected function dumpPropPrefix(array $info) protected function dumpString($val, Abstraction $abs = null) { $addQuotes = $this->getDumpOpt('addQuotes'); - if (\is_numeric($val)) { - $date = $this->checkTimestamp($val, $abs); - if ($addQuotes) { - $val = '"' . $val . '"'; - } - return $date - ? '📅 ' . $val . ' (' . $date . ')' - : $val; - } + $date = \is_numeric($val) + ? $this->checkTimestamp($val, $abs) + : null; $val = $this->debug->utf8->dump($val); if ($addQuotes) { $val = '"' . $val . '"'; } + if ($date) { + return '📅 ' . $val . ' (' . $date . ')'; + } $diff = $abs && $abs['strlen'] ? $abs['strlen'] - \strlen($abs['value']) : 0; @@ -281,6 +294,8 @@ protected function dumpUndefined() * @param Abstraction $abs resource abstraction * * @return string + * + * @SuppressWarnings(PHPMD.DevelopmentCodeFragment) */ protected function dumpUnknown(Abstraction $abs) { diff --git a/src/Debug/Framework/Cake4.php b/src/Debug/Framework/Cake4.php index 964d44ea..80feefe1 100644 --- a/src/Debug/Framework/Cake4.php +++ b/src/Debug/Framework/Cake4.php @@ -6,14 +6,14 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Framework; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Collector\Pdo; use bdk\Debug\Psr15\Middleware; use bdk\ErrorHandler\Error; @@ -179,12 +179,12 @@ protected function logEvents() 'subject' => $debug->abstracter->crateWithVals( \get_class($event->getSubject()), array( - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, ) ), ); } - $events[$name]['count'] ++; + $events[$name]['count']++; } \ksort($events); $debug->table('dispatched events', \array_values($events)); diff --git a/src/Debug/Framework/Laravel/Middleware.php b/src/Debug/Framework/Laravel/Middleware.php index f8a15439..457c3d28 100644 --- a/src/Debug/Framework/Laravel/Middleware.php +++ b/src/Debug/Framework/Laravel/Middleware.php @@ -6,14 +6,14 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Framework\Laravel; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use Closure; use Error; use Exception; @@ -336,7 +336,7 @@ protected function logSession() $debug->log( $this->debug->abstracter->crateWithVals( \get_class($this->container['session']), - array('typeMore' => Abstracter::TYPE_STRING_CLASSNAME) + array('typeMore' => Type::TYPE_STRING_CLASSNAME) ) ); $debug->log( diff --git a/src/Debug/Framework/Laravel/ServiceProvider.php b/src/Debug/Framework/Laravel/ServiceProvider.php index 8213195c..92806f87 100644 --- a/src/Debug/Framework/Laravel/ServiceProvider.php +++ b/src/Debug/Framework/Laravel/ServiceProvider.php @@ -6,14 +6,14 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Framework\Laravel; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Collector\MonologHandler; use bdk\Debug\Collector\StatementInfo; use bdk\Debug\Framework\Laravel\CacheEventsSubscriber; @@ -163,7 +163,7 @@ private function buildModelCountTable(&$tableInfoRows) 'attribs' => array( 'data-file' => $ref->getFileName(), ), - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, )), ); } @@ -452,10 +452,10 @@ private function logViewParams(View $view) return $data; } foreach ($data as $k => $v) { - $type = $this->debug->abstracter->getType($v)[0]; + $type = $this->debug->abstracter->type->getType($v)[0]; $data[$k] = $type === 'object' ? $this->debug->abstracter->crateWithVals(\get_class($v), array( - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, )) : $type; } diff --git a/src/Debug/Framework/Yii1_1/Component.php b/src/Debug/Framework/Yii1_1/Component.php index c6451cb6..a2e1c636 100644 --- a/src/Debug/Framework/Yii1_1/Component.php +++ b/src/Debug/Framework/Yii1_1/Component.php @@ -6,15 +6,15 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Framework\Yii1_1; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Collector\Pdo; use bdk\Debug\Framework\Yii1_1\ErrorLogger; use bdk\Debug\Framework\Yii1_1\LogRoute; @@ -331,7 +331,7 @@ private function logAuthClass() $debug->log('authManager class', $debug->abstracter->crateWithVals( \get_class($authManager), array( - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, ) )); @@ -340,7 +340,7 @@ private function logAuthClass() $debug->log('accessManager class', $debug->abstracter->crateWithVals( \get_class($accessManager), array( - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, ) )); } @@ -377,7 +377,7 @@ protected function logSession() $debug->log('session class', $debug->abstracter->crateWithVals( \get_class($session), array( - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, ) )); diff --git a/src/Debug/Framework/Yii2/LogUser.php b/src/Debug/Framework/Yii2/LogUser.php index 137f3e8c..5a335720 100644 --- a/src/Debug/Framework/Yii2/LogUser.php +++ b/src/Debug/Framework/Yii2/LogUser.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -46,8 +46,6 @@ public function __construct(Module $debugModule) * @param Event $event Event instance * * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function onDebugOutput(Event $event) { diff --git a/src/Debug/Framework/Yii2/Module.php b/src/Debug/Framework/Yii2/Module.php index 638a61af..b5b11886 100644 --- a/src/Debug/Framework/Yii2/Module.php +++ b/src/Debug/Framework/Yii2/Module.php @@ -6,15 +6,15 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Framework\Yii2; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Collector\Pdo; use bdk\Debug\Framework\Yii2\LogTarget; use bdk\Debug\LogEntry; @@ -367,10 +367,10 @@ protected function logCollectedEvents() foreach ($tableData as &$info) { unset($info['index']); $info['senderClass'] = $this->debug->abstracter->crateWithVals($info['senderClass'], array( - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, )); $info['eventClass'] = $this->debug->abstracter->crateWithVals($info['eventClass'], array( - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, )); } @@ -403,7 +403,7 @@ protected function logSession() $debug->log('session class', $debug->abstracter->crateWithVals( \get_class($session), array( - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, ) )); diff --git a/src/Debug/Plugin/AbstractLogReqRes.php b/src/Debug/Plugin/AbstractLogReqRes.php index 83e9bf0c..2b9992f4 100644 --- a/src/Debug/Plugin/AbstractLogReqRes.php +++ b/src/Debug/Plugin/AbstractLogReqRes.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -45,8 +45,8 @@ class AbstractLogReqRes */ protected function assertCorrectContentType($contentTypeDetected, $contentTypeUser, $requestMethod = null) { - $contentTypeDetected = \preg_replace('/\s*[;,].*$/', '', (string) $contentTypeDetected) ?: null; - $contentTypeUser = \preg_replace('/\s*[;,].*$/', '', (string) $contentTypeUser) ?: null; + $contentTypeDetected = \preg_replace('/\s*[;,].*$/', '', (string) $contentTypeDetected); + $contentTypeUser = \preg_replace('/\s*[;,].*$/', '', (string) $contentTypeUser); if ($contentTypeDetected === $contentTypeUser) { return; } @@ -60,20 +60,15 @@ protected function assertCorrectContentType($contentTypeDetected, $contentTypeUs ? 'with the wrong Content-Type' : 'without a Content-Type header' ); - if ( - $requestMethod === 'POST' - && \in_array($contentTypeUser, array(ContentType::FORM, ContentType::FORM_MULTIPART), true) - ) { + $formTypes = array(ContentType::FORM, ContentType::FORM_MULTIPART); + if ($requestMethod === 'POST' && \in_array($contentTypeUser, $formTypes, true)) { $message .= "\n" . 'Pay no attention to $_POST and instead use php://input'; } - $this->debug->warn( - $message, - $this->debug->meta(array( - 'detectFiles' => false, - 'file' => null, - 'line' => null, - )) - ); + $this->debug->warn($message, $this->debug->meta(array( + 'detectFiles' => false, + 'file' => null, + 'line' => null, + ))); } /** diff --git a/src/Debug/Plugin/Channel.php b/src/Debug/Plugin/Channel.php index 5b53fc69..94f7a5c1 100644 --- a/src/Debug/Plugin/Channel.php +++ b/src/Debug/Plugin/Channel.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -38,8 +38,8 @@ class Channel implements SubscriberInterface * Channels can be used to categorize log data... for example, may have a framework channel, database channel, library-x channel, etc * Channels may have subchannels * - * @param string $name channel name - * @param array $config channel specific configuration + * @param string|array $name channel name (or channel path) + * @param array $config channel specific configuration * * @return \bdk\Debug new or existing `Debug` instance */ diff --git a/src/Debug/Plugin/ConfigEvents.php b/src/Debug/Plugin/ConfigEvents.php index e68f6abc..ff78efb4 100644 --- a/src/Debug/Plugin/ConfigEvents.php +++ b/src/Debug/Plugin/ConfigEvents.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -95,7 +95,7 @@ public function onConfig(Event $event) private function onCfgChannels($val) { $tree = array(); - foreach ($val as $name => $config) { + \array_walk($val, static function ($config, $name) use (&$tree) { $ref = &$tree; $path = \explode('.', $name); $name = \array_pop($path); @@ -112,7 +112,7 @@ private function onCfgChannels($val) $ref[$name] = array(); } $ref[$name] = \array_merge($ref[$name], $config); - } + }); return $tree; } @@ -162,7 +162,6 @@ private function onCfgEmail($val, $key, Event $event) * @return string * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter */ private function onCfgKey($val, $name, Event $event) diff --git a/src/Debug/Plugin/Highlight.php b/src/Debug/Plugin/Highlight.php index 8c7eeea3..b7a0f8be 100644 --- a/src/Debug/Plugin/Highlight.php +++ b/src/Debug/Plugin/Highlight.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -23,10 +23,8 @@ class Highlight implements AssetProviderInterface * {@inheritDoc} * * @return array - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function getAssets() + public function getAssets() // phpcs:ignore SlevomatCodingStandard.Functions.FunctionLength { return array( 'css' => array( diff --git a/src/Debug/Plugin/InternalEvents.php b/src/Debug/Plugin/InternalEvents.php index 0df81f02..316068ae 100644 --- a/src/Debug/Plugin/InternalEvents.php +++ b/src/Debug/Plugin/InternalEvents.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -228,15 +228,7 @@ public function onShutdownLow() */ private function exitCheck() { - if ($this->debug->getCfg('exitCheck', Debug::CONFIG_DEBUG) === false) { - return; - } - if ($this->debug->data->get('outputSent')) { - return; - } - $lastError = $this->debug->errorHandler->getLastError(); - if ($lastError && ($lastError['type'] === E_PARSE || $lastError['exception'] instanceof \ParseError)) { - // parse error + if ($this->shouldExitCheck() === false) { return; } $findExit = $this->debug->findExit; @@ -257,6 +249,18 @@ private function exitCheck() } } + /** + * Shoule we force output for the given error + * + * @param Error $error Error instance + * + * @return bool + */ + private function forceErrorOutput(Error $error) + { + return $error->isFatal() && $this->debug->isCli() && $this->debug->getCfg('route') instanceof Stream; + } + /** * Log error * @@ -277,7 +281,7 @@ private function logError(Error $error) $error['message'], $error['fileAndLine'], $this->debug->meta(array( - 'context' => $error['category'] === 'fatal' && $error['backtrace'] === null + 'context' => $error['category'] === Error::CAT_FATAL && $error['backtrace'] === null ? $error['context'] : null, 'errorCat' => $error['category'], @@ -302,15 +306,21 @@ private function logError(Error $error) } /** - * Shoule we force output for the given error - * - * @param Error $error Error instance + * Determine if should perform `exit` search * * @return bool */ - private function forceErrorOutput(Error $error) + private function shouldExitCheck() { - return $error->isFatal() && $this->debug->isCli() && $this->debug->getCfg('route') instanceof Stream; + if ($this->debug->getCfg('exitCheck', Debug::CONFIG_DEBUG) === false) { + return false; + } + if ($this->debug->data->get('outputSent')) { + return false; + } + $lastError = $this->debug->errorHandler->getLastError(); + $isParseError = $lastError && ($lastError['type'] === E_PARSE || $lastError['exception'] instanceof \ParseError); + return $isParseError === false; } /** diff --git a/src/Debug/Plugin/LogFiles.php b/src/Debug/Plugin/LogFiles.php index 74722933..5b97068e 100644 --- a/src/Debug/Plugin/LogFiles.php +++ b/src/Debug/Plugin/LogFiles.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -144,7 +144,7 @@ private function exclude($path) if (!isset($this->excludedCounts[$path])) { $this->excludedCounts[$path] = 0; } - $this->excludedCounts[$path] ++; + $this->excludedCounts[$path]++; } /** diff --git a/src/Debug/Plugin/LogPhp.php b/src/Debug/Plugin/LogPhp.php index a94d3b85..82902d17 100644 --- a/src/Debug/Plugin/LogPhp.php +++ b/src/Debug/Plugin/LogPhp.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -97,6 +97,7 @@ protected function logPhpInfo() 'valCompare' => '', )); $this->assertExtensions(); + $this->assertMbStringSettings(); $this->logXdebug(); } @@ -171,11 +172,15 @@ private function assertExtensions() )) ); } - $this->assertSetting(array( - 'msg' => 'Multibyte string function overloading is enabled (is evil)', - 'name' => 'mbstring.func_overload', - 'valCompare' => false, - )); + } + + /** + * Assert MbString settings + * + * @return void + */ + private function assertMbStringSettings() + { if (PHP_VERSION_ID < 50600) { // default_charset should be used for php >= 5.6 $this->assertSetting(array( @@ -184,6 +189,11 @@ private function assertExtensions() 'valCompare' => '', )); } + $this->assertSetting(array( + 'msg' => 'Multibyte string function overloading is enabled (is evil)', + 'name' => 'mbstring.func_overload', + 'valCompare' => false, + )); } /** diff --git a/src/Debug/Plugin/LogRequest.php b/src/Debug/Plugin/LogRequest.php index ee7346a9..720c3123 100644 --- a/src/Debug/Plugin/LogRequest.php +++ b/src/Debug/Plugin/LogRequest.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -137,16 +137,14 @@ private function logInput($method, $contentType) || $methodHasBody || $request->getHeaderLine('Content-Length') || $request->getHeaderLine('Transfer-Encoding'); - if ($logInput === false) { - return; - } - // we have post body or we expect post body $meta = $this->debug->meta(array( 'detectFiles' => false, 'file' => null, 'line' => null, )); - if ($input) { + if ($logInput === false) { + return; + } elseif ($input) { if ($methodHasBody === false) { $this->debug->warn($method . ' request with body', $meta); } diff --git a/src/Debug/Plugin/Method/Basic.php b/src/Debug/Plugin/Method/Basic.php index 1b47b4fb..6cec1964 100644 --- a/src/Debug/Plugin/Method/Basic.php +++ b/src/Debug/Plugin/Method/Basic.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -44,7 +44,7 @@ class Basic implements SubscriberInterface * Supports styling & substitutions * * @param bool $assertion Any boolean expression. If the assertion is false, the message is logged - * @param mixed $msg,... (optional) variable num of values to output if assertion fails + * @param mixed ...$msg (optional) variable num of values to output if assertion fails * if none provided, will use calling file & line num * * @return $this @@ -82,7 +82,7 @@ public function assert($assertion, $msg = null) * * Supports styling & substitutions * - * @param mixed $arg,... message / values + * @param mixed ...$arg message / values * * @return $this */ @@ -97,7 +97,7 @@ public function error() * * Supports styling & substitutions * - * @param mixed $arg,... message / values + * @param mixed ...$arg message / values * * @return $this */ @@ -127,7 +127,7 @@ public function getSubscriptions() * * Supports styling & substitutions * - * @param mixed $arg,... message / values + * @param mixed ...$arg. message / values * * @return $this */ @@ -173,7 +173,7 @@ public function onBootstrap(Event $event) * * Similar to php's `var_dump()`. Dump values immediately * - * @param mixed $arg,... message / values + * @param mixed ...$arg. message / values * * @return void */ @@ -205,7 +205,7 @@ public function varDump() * * Supports styling & substitutions * - * @param mixed $arg,... message / values + * @param mixed ...$arg message / values * * @return $this */ diff --git a/src/Debug/Plugin/Method/Clear.php b/src/Debug/Plugin/Method/Clear.php index 7ad2e333..21b373fb 100644 --- a/src/Debug/Plugin/Method/Clear.php +++ b/src/Debug/Plugin/Method/Clear.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -58,7 +58,7 @@ public function __construct() * * @return $this */ - public function clear($bitmask = Debug::CLEAR_LOG) + public function clear($bitmask = Debug::CLEAR_LOG) // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter { $debug = $this->debug; $logEntry = new LogEntry( diff --git a/src/Debug/Plugin/Method/Group.php b/src/Debug/Plugin/Method/Group.php index 7122e845..17f371ae 100644 --- a/src/Debug/Plugin/Method/Group.php +++ b/src/Debug/Plugin/Method/Group.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -103,7 +103,7 @@ public function getSubscriptions() * ungroup: false // when closed: if no children, convert to plain log entry * // when closed: if only one child, remove the containing group * - * @param mixed $arg,... label / values + * @param mixed ...$arg. label / values * * @return $this */ @@ -122,7 +122,7 @@ public function group() * * Unline `group()`, `groupCollapsed()`, will initially be collapsed * - * @param mixed $arg,... label / values + * @param mixed ...$arg label / values * * @return $this */ diff --git a/src/Debug/Plugin/Method/Profile.php b/src/Debug/Plugin/Method/Profile.php index cd81d9ef..6bf0c3ad 100644 --- a/src/Debug/Plugin/Method/Profile.php +++ b/src/Debug/Plugin/Method/Profile.php @@ -6,15 +6,15 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ namespace bdk\Debug\Plugin\Method; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; use bdk\Debug\Plugin\CustomMethodTrait; use bdk\Debug\Utility\FileStreamWrapper; @@ -215,43 +215,51 @@ private function doProfileEnd(LogEntry $logEntry) $logEntry['meta']['name'] = \key($this->instances); } $name = $logEntry['meta']['name']; - // set default args - $args = array( - $name !== null + $logEntry['args'] = isset($this->instances[$name]) + ? $this->doProfileEndArgs($logEntry) + : ($name !== null ? 'profileEnd: No such Profile: ' . $name - : 'profileEnd: Not currently profiling', - ); - if (isset($this->instances[$name])) { - $instance = $this->instances[$name]; - $data = $instance->end(); - $tableInfo = $logEntry->getMeta('tableInfo', array()); - $tableInfo = \array_replace_recursive(array( - 'rows' => \array_fill_keys(\array_keys($data), array()), - ), $tableInfo); - foreach (\array_keys($data) as $k) { - $tableInfo['rows'][$k]['key'] = new Abstraction( - Abstracter::TYPE_CALLABLE, - array( - 'hideType' => true, // don't output 'callable' - 'value' => $k, - ) - ); - } - $caption = 'Profile \'' . $name . '\' Results'; - $args = array($caption, 'no data'); - if ($data) { - $args = array($data); - $logEntry->setMeta(array( - 'caption' => $caption, - 'tableInfo' => $tableInfo, - 'totalCols' => array('ownTime'), - )); - } - unset($this->instances[$name]); - } - $logEntry['args'] = $args; + : 'profileEnd: Not currently profiling' + ); $debug->rootInstance->getPlugin('methodTable')->doTable($logEntry); $debug->log($logEntry); + unset($this->instances[$name]); + } + + /** + * Build table data and info + * + * @param LogEntry $logEntry LogEntry instance + * + * @return array + */ + private function doProfileEndArgs(LogEntry $logEntry) + { + $name = $logEntry['meta']['name']; + $caption = 'Profile \'' . $name . '\' Results'; + $instance = $this->instances[$name]; + $data = $instance->end(); + if (!$data) { + return array($caption, 'no data'); + } + $tableInfo = \array_replace_recursive(array( + 'rows' => \array_fill_keys(\array_keys($data), array()), + ), $logEntry->getMeta('tableInfo', array())); + foreach (\array_keys($data) as $k) { + $tableInfo['rows'][$k]['key'] = new Abstraction( + Type::TYPE_CALLABLE, + array( + 'hideType' => true, // don't output 'callable' + 'value' => $k, + ) + ); + } + $logEntry->setMeta(array( + 'caption' => $caption, + 'tableInfo' => $tableInfo, + 'totalCols' => array('ownTime'), + )); + return array($data); } /** @@ -264,7 +272,6 @@ private function doProfileEnd(LogEntry $logEntry) * @return bool * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter */ private function onCfgEnableProfiling($val, $key, Event $event) diff --git a/src/Debug/Plugin/Method/ReqRes.php b/src/Debug/Plugin/Method/ReqRes.php index 36f0084b..c168c63d 100644 --- a/src/Debug/Plugin/Method/ReqRes.php +++ b/src/Debug/Plugin/Method/ReqRes.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -173,7 +173,7 @@ public function getResponseHeaders($asString = false) $protocol = $this->getServerParam('SERVER_PROTOCOL') ?: 'HTTP/1.0'; $code = $this->getResponseCode(); $headersAll = array( - $protocol . ' ' . $code .' ' . Response::codePhrase($code), + $protocol . ' ' . $code . ' ' . Response::codePhrase($code), ); foreach ($headers as $k => $vals) { foreach ($vals as $val) { diff --git a/src/Debug/Plugin/Method/Table.php b/src/Debug/Plugin/Method/Table.php index 3daeffc4..b5927a95 100644 --- a/src/Debug/Plugin/Method/Table.php +++ b/src/Debug/Plugin/Method/Table.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -52,7 +52,7 @@ public function __construct() * 2nd encountered array (optional) specifies columns to output * 1st encountered string is a label/caption * - * @param mixed $arg,... traversable, [option array], [caption] in no particular order + * @param mixed ...$arg traversable, [option array], [caption] in no particular order * * @return $this */ @@ -95,8 +95,23 @@ public function doTable(LogEntry $logEntry) $logEntry->setMeta('cfg', null); } - $this->initLogEntry($logEntry); + $this->doTableLogEntry($logEntry); + + if ($cfgRestore) { + $this->debug->setCfg($cfgRestore, Debug::CONFIG_NO_RETURN); + } + } + /** + * Find the data, caption, & columns in logEntry arguments + * + * @param LogEntry $logEntry log entry instance + * + * @return void + */ + private function doTableLogEntry(LogEntry $logEntry) + { + $this->initLogEntry($logEntry); $table = new TableProcessor( isset($logEntry['args'][0]) ? $logEntry['args'][0] @@ -105,16 +120,13 @@ public function doTable(LogEntry $logEntry) $this->debug ); - if ($cfgRestore) { - $this->debug->setCfg($cfgRestore, Debug::CONFIG_NO_RETURN); - } - if ($table->haveRows()) { $logEntry['args'] = array($table->getRows()); $logEntry['meta'] = $table->getMeta(); return; } + // no data... create logEntry instead $logEntry['method'] = 'log'; if ($logEntry->getMeta('caption')) { \array_unshift($logEntry['args'], $logEntry->getMeta('caption')); diff --git a/src/Debug/Plugin/Method/Time.php b/src/Debug/Plugin/Method/Time.php index 78b9f933..be4d2ee0 100644 --- a/src/Debug/Plugin/Method/Time.php +++ b/src/Debug/Plugin/Method/Time.php @@ -158,8 +158,8 @@ public function timeGet($label = null, $log = true, $return = 'auto') * * also logs additional arguments * - * @param string $label (optional) unique label - * @param mixed $arg,... (optional) additional values to be logged with time + * @param string $label (optional) unique label + * @param mixed ...$arg (optional) additional values to be logged with time * * @return $this */ diff --git a/src/Debug/Plugin/Redaction.php b/src/Debug/Plugin/Redaction.php index d568fd37..41c41d9d 100644 --- a/src/Debug/Plugin/Redaction.php +++ b/src/Debug/Plugin/Redaction.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -14,8 +14,8 @@ use bdk\Debug; use bdk\Debug\AbstractComponent; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; use bdk\Debug\Plugin\CustomMethodTrait; use bdk\PubSub\Event; @@ -322,10 +322,10 @@ private function parseHeaders($headers) $headerLines = \explode("\r\n", \trim($headers)); $startLine = null; $headers = array(); - foreach ($headerLines as $i => $line) { + \array_walk($headerLines, static function ($line, $i) use (&$headers, &$startLine) { if ($i === 0 && \strpos($line, ':') === false) { $startLine = $line; - continue; + return; } list($name, $value) = \array_replace(array(null, null), \explode(':', $line, 2)); $name = \trim($name); @@ -335,7 +335,7 @@ private function parseHeaders($headers) $headers[$name][] = $value !== null ? \trim($value) : null; - } + }); return array($startLine, $headers); } @@ -348,7 +348,7 @@ private function parseHeaders($headers) */ private function redactAbstraction(Abstraction $abs) { - if ($abs['type'] === Abstracter::TYPE_OBJECT) { + if ($abs['type'] === Type::TYPE_OBJECT) { $abs['properties'] = $this->redact($abs['properties']); $abs['stringified'] = $this->redact($abs['stringified']); if (isset($abs['methods']['__toString']['returnValue'])) { diff --git a/src/Debug/Psr3/MethodSignatureCompatTrait.php b/src/Debug/Psr3/MethodSignatureCompatTrait.php index b6e282bb..fd96c2c6 100644 --- a/src/Debug/Psr3/MethodSignatureCompatTrait.php +++ b/src/Debug/Psr3/MethodSignatureCompatTrait.php @@ -1,4 +1,4 @@ - * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -212,6 +212,19 @@ protected function getDumper() return $this->dumper; } + /** + * Get request-method + request-uri or command line args + * + * @return string + */ + protected function getRequestMethodUri() + { + return $this->debug->isCli() + ? '$: ' . \implode(' ', $this->debug->getServerParam('argv', array())) + : $this->debug->serverRequest->getMethod() + . ' ' . $this->debug->redact((string) $this->debug->serverRequest->getUri()); + } + /** * Publish Debug::EVENT_OUTPUT_LOG_ENTRY. * Return event['return'] if not empty diff --git a/src/Debug/Route/ChromeLogger.php b/src/Debug/Route/ChromeLogger.php index cbe634ed..b23c759d 100644 --- a/src/Debug/Route/ChromeLogger.php +++ b/src/Debug/Route/ChromeLogger.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 * * @see https://craig.is/writing/chrome-logger/techspecs @@ -16,6 +16,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; use bdk\PubSub\Event; @@ -160,11 +161,7 @@ protected function buildJsonData() $this->processAlerts(); $this->processSummary(); $this->processLog(); - $heading = array('PHP', $this->debug->isCli() - ? '$: ' . \implode(' ', $this->debug->getServerParam('argv', array())) - : $this->debug->serverRequest->getMethod() - . ' ' . $this->debug->redact((string) $this->debug->serverRequest->getUri()), - ); + $heading = array('PHP', $this->getRequestMethodUri()); if (!$this->cfg['group']) { \array_unshift($this->jsonData['rows'], array($heading, null, 'info')); return; @@ -341,8 +338,8 @@ protected function translateJsonValues($json) { return \str_replace( array( - \json_encode(Abstracter::TYPE_FLOAT_INF), - \json_encode(Abstracter::TYPE_FLOAT_NAN), + \json_encode(Type::TYPE_FLOAT_INF), + \json_encode(Type::TYPE_FLOAT_NAN), \json_encode(Abstracter::UNDEFINED), ), array( diff --git a/src/Debug/Route/Discord.php b/src/Debug/Route/Discord.php index 2dc37e72..290399a2 100644 --- a/src/Debug/Route/Discord.php +++ b/src/Debug/Route/Discord.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -114,11 +114,7 @@ private function buildMessage(Error $error) : ':warning:'; return array( 'content' => $emoji . ' **' . $error['typeStr'] . '**' . "\n" - . ($this->debug->isCli() - ? '$: ' . \implode(' ', $this->debug->getServerParam('argv', array())) - : $this->debug->serverRequest->getMethod() - . ' ' . $this->debug->redact((string) $this->debug->serverRequest->getUri()) - ) . "\n" + . $this->getRequestMethodUri() . "\n" . $error->getMessageText() . "\n" . $error['fileAndLine'], ); diff --git a/src/Debug/Route/Firephp.php b/src/Debug/Route/Firephp.php index f4ff6ac5..fd8da2d6 100644 --- a/src/Debug/Route/Firephp.php +++ b/src/Debug/Route/Firephp.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -72,15 +72,10 @@ public function processLogEntries(Event $event) $event['headers'][] = array('X-Wf-Protocol-1', 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'); $event['headers'][] = array('X-Wf-1-Plugin-1', 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/' . self::FIREPHP_PROTO_VER); $event['headers'][] = array('X-Wf-1-Structure-1', 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'); - // FirePHP in a CLI environment?? Sure, why not. - $heading = $this->debug->isCli() - ? '$: ' . \implode(' ', $this->debug->getServerParam('argv', array())) - : $this->debug->serverRequest->getMethod() - . ' ' . $this->debug->redact((string) $this->debug->serverRequest->getUri()); $this->processLogEntryViaEvent(new LogEntry( $this->debug, 'groupCollapsed', - array('PHP: ' . $heading) + array('PHP: ' . $this->getRequestMethodUri()) )); $this->processAlerts(); $this->processSummary(); diff --git a/src/Debug/Route/Html/Tabs.php b/src/Debug/Route/Html/Tabs.php index 51f43dad..f5d5e38b 100644 --- a/src/Debug/Route/Html/Tabs.php +++ b/src/Debug/Route/Html/Tabs.php @@ -1,5 +1,15 @@ + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.0 + */ + namespace bdk\Debug\Route\Html; use bdk\Debug; @@ -159,7 +169,19 @@ private function buildTabPane(Debug $debug) ), 'role' => 'tabpanel', ), - "\n" . '
    ' . "\n" + $this->buildTabPaneBody() + ) . "\n"; + } + + /** + * Build primary tab pane body + * + * @return string html + */ + private function buildTabPaneBody() + { + return "\n" + . '
    ' . "\n" . $this->route->processAlerts() /* If outputing script, initially hide the output.. @@ -172,8 +194,7 @@ private function buildTabPane(Debug $debug) . '
      ' . "\n" . $this->route->processLog() . '
    ' . "\n" - . '
    ' . "\n" // close .tab-body - ) . "\n"; + . '
    ' . "\n"; // close .tab-body } /** diff --git a/src/Debug/Route/Script.php b/src/Debug/Route/Script.php index f134f8b4..49704353 100644 --- a/src/Debug/Route/Script.php +++ b/src/Debug/Route/Script.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -14,6 +14,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; use bdk\PubSub\Event; @@ -68,27 +69,14 @@ public function processlogEntries(Event $event) { $this->dumper->crateRaw = false; $this->data = $this->debug->data->get(); - $errorStats = $this->debug->errorStats(); - $errorStr = ''; - if ($errorStats['inConsole']) { - $errorStr = 'Errors: '; - foreach ($errorStats['counts'] as $category => $vals) { - $errorStr .= $vals['inConsole'] . ' ' . $category . ', '; - } - $errorStr = \substr($errorStr, 0, -2); - } - $str = ''; - $str .= ' doesn't appear inside our ', - \json_encode(Abstracter::TYPE_FLOAT_INF), - \json_encode(Abstracter::TYPE_FLOAT_NAN), + \json_encode(Type::TYPE_FLOAT_INF), + \json_encode(Type::TYPE_FLOAT_NAN), \json_encode(Abstracter::UNDEFINED), ), array( @@ -156,4 +128,58 @@ public function processLogEntry(LogEntry $logEntry) ); return $str; } + + /** + * Build the console.xxxx() call + * + * @param LogEntry $logEntry LogEntry instance + * + * @return string + */ + protected function buildConsoleCall(LogEntry $logEntry) + { + $method = $logEntry['method']; + $args = $logEntry['args']; + $meta = $logEntry['meta']; + switch ($method) { + case 'assert': + \array_unshift($args, false); + break; + case 'error': + case 'warn': + if (isset($meta['file'])) { + $args[] = \sprintf('%s: line %s', $meta['file'], $meta['line']); + } + break; + case 'table': + $args = $this->dumper->valDumper->dump($args); + break; + default: + if (\in_array($method, $this->consoleMethods, true) === false) { + $method = 'log'; + } + } + $args = \json_encode($args, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + $args = \substr($args, 1, -1); + return 'console.' . $method . '(' . $args . ');' . "\n"; + } + + /** + * Get number of errors per category + * + * @return string + */ + private function getErrorSummary() + { + $errorStats = $this->debug->errorStats(); + $errorStr = ''; + if ($errorStats['inConsole']) { + $errorStr = 'Errors: '; + foreach ($errorStats['counts'] as $category => $vals) { + $errorStr .= $vals['inConsole'] . ' ' . $category . ', '; + } + $errorStr = \substr($errorStr, 0, -2); + } + return $errorStr; + } } diff --git a/src/Debug/Route/Slack.php b/src/Debug/Route/Slack.php index 6c6cbc15..8bd5c66d 100644 --- a/src/Debug/Route/Slack.php +++ b/src/Debug/Route/Slack.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -157,10 +157,7 @@ private function buildMessages(Error $error) ->withHeader($icon . ' ' . $error['typeStr']) ->withText($icon . ' ' . $error['typeStr'] . "\n" . $error->getMessageText()) ->withContext(array( - $this->debug->isCli() - ? '$: ' . \implode(' ', $this->debug->getServerParam('argv', array())) - : $this->debug->serverRequest->getMethod() - . ' ' . $this->debug->redact((string) $this->debug->serverRequest->getUri()), + $this->getRequestMethodUri(), )) ->withSection(array( 'text' => $error->getMessageText(), diff --git a/src/Debug/Route/Teams.php b/src/Debug/Route/Teams.php index 5eea8f65..4bf3bcd0 100644 --- a/src/Debug/Route/Teams.php +++ b/src/Debug/Route/Teams.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -155,10 +155,7 @@ private function buildMessage(Error $error) ) ->withAddedElement( (new TeamsTextBlock( - $this->debug->isCli() - ? '$: ' . \implode(' ', $this->debug->getServerParam('argv', array())) - : $this->debug->serverRequest->getMethod() - . ' ' . $this->debug->redact((string) $this->debug->serverRequest->getUri()) + $this->getRequestMethodUri() ))->withIsSubtle() ) ->withAddedElement( diff --git a/src/Debug/Route/Wamp.php b/src/Debug/Route/Wamp.php index 6304af47..5d232577 100644 --- a/src/Debug/Route/Wamp.php +++ b/src/Debug/Route/Wamp.php @@ -9,7 +9,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -241,25 +241,7 @@ public function processLogEntries(Event $event = null) $this->processLogEntryViaEvent($logEntry); } foreach ($data['logSummary'] as $priority => $entries) { - $this->processLogEntryViaEvent(new LogEntry( - $this->debug, - 'groupSummary', - array(), - array( - 'priority' => $priority, - ) - )); - foreach ($entries as $logEntry) { - $this->processLogEntryViaEvent($logEntry); - } - $this->processLogEntryViaEvent(new LogEntry( - $this->debug, - 'groupEnd', - array(), - array( - 'closesSummary' => true, - ) - )); + $this->processLogSummaryEntries($priority, $entries); } foreach ($data['log'] as $logEntry) { $this->processLogEntryViaEvent($logEntry); @@ -296,22 +278,7 @@ public function processLogEntry(LogEntry $logEntry) list($args, $metaNew, $classesNew) = $this->crate->crateLogEntry($logEntry); $meta = \array_merge($meta, $metaNew); } - if ($classesNew) { - $classDefinitions = array(); - foreach ($classesNew as $classKey) { - $classDefinitions[$classKey] = $this->debug->data->get('classDefinitions/' . $classKey); - } - $this->processLogEntry(new LogEntry( - $this->debug, - 'meta', - array( - array( - 'classDefinitions' => $classDefinitions, - ), - ), - $meta - )); - } + $this->processNewClasses($classesNew, $meta); $this->wamp->publish($this->topic, array($logEntry['method'], $args, $meta)); } @@ -338,6 +305,64 @@ protected function processLogEntryViaEvent(LogEntry $logEntry) $this->processLogEntry($logEntry); } + /** + * Process logSummary priority + * + * @param int $priority Priority + * @param LogEntry[] $entries LogEntries + * + * @return void + */ + private function processLogSummaryEntries($priority, array $entries) + { + $this->processLogEntryViaEvent(new LogEntry( + $this->debug, + 'groupSummary', + array(), + array( + 'priority' => $priority, + ) + )); + foreach ($entries as $logEntry) { + $this->processLogEntryViaEvent($logEntry); + } + $this->processLogEntryViaEvent(new LogEntry( + $this->debug, + 'groupEnd', + array(), + array( + 'closesSummary' => true, + ) + )); + } + + /** + * Process class definitions that have not yet been pushed + * + * @param array $classesNew New classnames + * + * @return void + */ + private function processNewClasses(array $classesNew) + { + if (!$classesNew) { + return; + } + $classDefinitions = array(); + foreach ($classesNew as $classKey) { + $classDefinitions[$classKey] = $this->debug->data->get('classDefinitions/' . $classKey); + } + $this->processLogEntry(new LogEntry( + $this->debug, + 'meta', + array( + array( + 'classDefinitions' => $classDefinitions, + ), + ) + )); + } + /** * Publish initial meta data * diff --git a/src/Debug/Route/WampCrate.php b/src/Debug/Route/WampCrate.php index eae833f8..9b8ceb76 100644 --- a/src/Debug/Route/WampCrate.php +++ b/src/Debug/Route/WampCrate.php @@ -6,15 +6,15 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Route; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; /** @@ -129,17 +129,17 @@ private function crateAbstraction(Abstraction $abs) { $clone = clone $abs; switch ($clone['type']) { - case Abstracter::TYPE_ARRAY: + case Type::TYPE_ARRAY: $clone['value'] = $this->crateArray($clone['value']); return $clone; - case Abstracter::TYPE_OBJECT: + case Type::TYPE_OBJECT: return $this->crateObject($clone); - case Abstracter::TYPE_STRING: + case Type::TYPE_STRING: $clone['value'] = $this->crateString( $clone['value'], - $clone['typeMore'] === Abstracter::TYPE_STRING_BINARY + $clone['typeMore'] === Type::TYPE_STRING_BINARY ); - if ($clone['typeMore'] === Abstracter::TYPE_STRING_BINARY) { + if ($clone['typeMore'] === Type::TYPE_STRING_BINARY) { // PITA to get strlen in javascript // pass the length of captured value $clone['strlenValue'] = \strlen($abs['value']); diff --git a/src/Debug/ServiceProvider.php b/src/Debug/ServiceProvider.php index 63b529ed..8e3e01dd 100644 --- a/src/Debug/ServiceProvider.php +++ b/src/Debug/ServiceProvider.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -27,10 +27,8 @@ class ServiceProvider implements ServiceProviderInterface * @param Container $container Container instances * * @return void - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function register(Container $container) + public function register(Container $container) // phpcs:ignore SlevomatCodingStandard.Functions.FunctionLength { /* These "services" are reused between channels diff --git a/src/Debug/Utility/ArrayUtil.php b/src/Debug/Utility/ArrayUtil.php index 9ebb65cb..7f239460 100644 --- a/src/Debug/Utility/ArrayUtil.php +++ b/src/Debug/Utility/ArrayUtil.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -28,7 +28,7 @@ class ArrayUtil * * @return array */ - public static function copy($source, $deep = true) + public static function copy(array $source, $deep = true) { $arr = array(); foreach ($source as $key => $val) { @@ -46,12 +46,12 @@ public static function copy($source, $deep = true) * * Returns an array containing all the values from array that are not present in any of the other arrays. * - * @param array $array array to compare from - * @param array $arrays,... arrays to compare against + * @param array $array array to compare from + * @param array ...$arrays arrays to compare against * * @return array */ - public static function diffAssocRecursive($array, $arrays) + public static function diffAssocRecursive(array $array, $arrays) { $arrays = \func_get_args(); \array_shift($arrays); @@ -93,7 +93,7 @@ public static function isList($val) * * @return array */ - public static function mapRecursive($callback, $input) + public static function mapRecursive($callback, array $input) { return \array_map(static function ($val) use ($callback) { return \is_array($val) @@ -105,12 +105,12 @@ public static function mapRecursive($callback, $input) /** * Recursively merge arrays * - * @param array $arrayDef default array - * @param array $array2,... array to merge + * @param array $arrayDef default array + * @param array ...$array2 array to merge * * @return array */ - public static function mergeDeep($arrayDef, $array2) + public static function mergeDeep(array $arrayDef, $array2) { $mergeArrays = \func_get_args(); \array_shift($mergeArrays); @@ -122,7 +122,7 @@ public static function mergeDeep($arrayDef, $array2) } /** - * Get value from array + * Get value from array (or obj with array access) * * @param array $array array to traverse * @param array|string $path key path @@ -135,7 +135,7 @@ public static function mergeDeep($arrayDef, $array2) * * @return mixed */ - public static function pathGet(&$array, $path, $default = null) + public static function pathGet(array &$array, $path, $default = null) { $path = \array_reverse(self::pathToArray($path)); while ($path) { @@ -143,20 +143,16 @@ public static function pathGet(&$array, $path, $default = null) $arrayAccess = \is_array($array) || $array instanceof \ArrayAccess; if (!$arrayAccess) { return $default; - } - if (isset($array[$key])) { + } elseif (isset($array[$key])) { $array = &$array[$key]; continue; - } - if ($key === '__count__') { + } elseif ($key === '__count__') { return \count($array); - } - if ($key === '__pop__') { + } elseif ($key === '__pop__') { $arrayNew = \array_pop($array); $array = &$arrayNew; continue; - } - if (self::specialKey($key, $path, $array)) { + } elseif (self::specialKey($key, $path, $array)) { continue; } return $default; @@ -177,19 +173,17 @@ public static function pathGet(&$array, $path, $default = null) * * @return void */ - public static function pathSet(&$array, $path, $val) + public static function pathSet(array &$array, $path, $val) { $path = \array_reverse(self::pathToArray($path)); while ($path) { $key = \array_pop($path); if (self::specialKey($key, $path, $array)) { continue; - } - if ($val === '__unset__' && empty($path)) { + } elseif ($val === '__unset__' && empty($path)) { unset($array[$key]); return; - } - if (!isset($array[$key]) || !\is_array($array[$key])) { + } elseif (!isset($array[$key]) || !\is_array($array[$key])) { $array[$key] = array(); // initialize this level } $array = &$array[$key]; @@ -206,7 +200,7 @@ public static function pathSet(&$array, $path, $val) * * @return array|false Returns empty array if value not found */ - public static function searchRecursive($value, $array, $inclKeys = false) + public static function searchRecursive($value, array $array, $inclKeys = false) { $key = \array_search($value, $array, true); if ($key !== false) { @@ -231,13 +225,13 @@ public static function searchRecursive($value, $array, $inclKeys = false) * Sort array, using `$order` * Keys will be preserved * - * @param array $array Array to sort - * @param array $order values that define order / should come first - * @param string $what ("value") or "key" - Whether to sort sort by value or key + * @param array $array Array to sort + * @param array|null $order values that define order / should come first + * @param string $what ("value") or "key" - Whether to sort sort by value or key * * @return void */ - public static function sortWithOrder(&$array, $order = array(), $what = 'value') + public static function sortWithOrder(array &$array, $order = array(), $what = 'value') { $callback = static function ($valA, $valB) use ($order) { $aPos = \array_search($valA, $order, true); @@ -273,7 +267,7 @@ public static function sortWithOrder(&$array, $order = array(), $what = 'value') * * @return array removed values */ - public static function spliceAssoc(&$array, $key, $length = null, $replacement = array()) + public static function spliceAssoc(array &$array, $key, $length = null, $replacement = array()) { $offset = \array_search($key, \array_keys($array), true); $count = \count($array); @@ -305,7 +299,7 @@ public static function spliceAssoc(&$array, $key, $length = null, $replacement = * * @return array An array containing all the values from array that are not present in array2 */ - private static function diffAssocRecursiveHelper($array, $array2) + private static function diffAssocRecursiveHelper(array $array, array $array2) { $diff = array(); \array_walk($array, static function ($value, $key) use (&$diff, $array2) { @@ -327,6 +321,18 @@ private static function diffAssocRecursiveHelper($array, $array2) return $diff; } + /** + * Check that value is not an array + * + * @param mixed $value Value to test + * + * @return bool + */ + private static function isNonMergeable($value) + { + return \is_array($value) === false || Php::isCallable($value, Php::IS_CALLABLE_ARRAY_ONLY); + } + /** * Merge 2nd array into first * @@ -335,10 +341,10 @@ private static function diffAssocRecursiveHelper($array, $array2) * * @return array */ - private static function mergeDeepWalk($arrayDef, $array2) + private static function mergeDeepWalk(array $arrayDef, array $array2) { \array_walk($array2, static function ($value, $key) use (&$arrayDef) { - if (\is_array($value) === false || Php::isCallable($value, Php::IS_CALLABLE_ARRAY_ONLY)) { + if (self::isNonMergeable($value)) { // not array or appears to be a callable if (\is_int($key) === false) { $arrayDef[$key] = $value; @@ -348,7 +354,8 @@ private static function mergeDeepWalk($arrayDef, $array2) } return; } - if (isset($arrayDef[$key]) === false || \is_array($arrayDef[$key]) === false || Php::isCallable($arrayDef[$key], Php::IS_CALLABLE_ARRAY_ONLY)) { + if (isset($arrayDef[$key]) === false || self::isNonMergeable($arrayDef[$key])) { + // default not set or can be overwritten without merge $arrayDef[$key] = $value; return; } @@ -385,7 +392,7 @@ private static function pathToArray($path) * * @return bool whether key was handled */ - private static function specialKey($key, &$path, &$array) + private static function specialKey($key, array &$path, array &$array) { if ($key === '__end__') { \end($array); diff --git a/src/Debug/Utility/FileStreamWrapper.php b/src/Debug/Utility/FileStreamWrapper.php index 13ba84ea..96b1cb9d 100644 --- a/src/Debug/Utility/FileStreamWrapper.php +++ b/src/Debug/Utility/FileStreamWrapper.php @@ -6,15 +6,13 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Utility; -use bdk\Debug; use bdk\Debug\Utility\Php; -use bdk\PubSub\Manager; /** * Generic stream-wrapper which publishes Debug::EVENT_STREAM_WRAP when file is required/included @@ -25,118 +23,13 @@ * @see http://php.net/manual/en/class.streamwrapper.php * * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + * @SuppressWarnings(PHPMD.TooManyPublicMethods) */ -class FileStreamWrapper +class FileStreamWrapper extends FileStreamWrapperBase { - const OUTPUT_ACCESS_MODE = 'rb+'; - const OUTPUT_DESTINATION = 'php://memory'; - const STREAM_OPEN_FOR_INCLUDE = 128; - - public static $filesTransformed = array(); - - /** - * @var resource - */ - public $context; - - protected static $isRegistered = false; - - /** - * @var string[] - */ - protected static $protocols = array('file', 'phar'); - - private static $eventManager; - - /** - * @var array paths to exclude from adding tick declaration - */ - private static $pathsExclude = array(); - - /** - * @var resource|null - */ + /** @var resource|null */ private $resource; - /** - * Register this stream wrapper - * - * @return void - * - * @throws \UnexpectedValueException - */ - public static function register() - { - if (static::$isRegistered) { - return; - } - foreach (static::$protocols as $protocol) { - self::registerProtocol($protocol); - } - static::$isRegistered = true; - /* - Disable OPcache - a) want to make sure we modify required files - b) don't want to cache modified files - */ - \ini_set('opcache.enable', '0'); - } - - /** - * Restore previous wrapper - * - * @return void - * - * @throws \UnexpectedValueException - */ - public static function unregister() - { - if (static::$isRegistered === false) { - return; - } - foreach (static::$protocols as $protocol) { - $result = \stream_wrapper_restore($protocol); - if ($result === false) { - throw new \UnexpectedValueException('Failed to restore stream wrapper for ' . $protocol); - } - } - static::$isRegistered = false; - } - - /** - * Define EventManager - * - * @param Manager $eventManager Event manager - * - * @return void - */ - public static function setEventManager(Manager $eventManager) - { - static::$eventManager = $eventManager; - } - - /** - * Set paths/directories to exclude - * - * @param string[] $pathsExclude paths/directories to exclude - * - * @return void - */ - public static function setPathsExclude(array $pathsExclude) - { - static::$pathsExclude = \array_unique(\array_map('realpath', $pathsExclude)); - } - - /** - * Get paths/directories to exclude - * - * @return string[] - */ - public static function getPathsExclude() - { - return static::$pathsExclude; - } - /** * Close the directory * @@ -596,6 +489,7 @@ public function url_stat($path, $flags) } if ($flags & STREAM_URL_STAT_QUIET) { // Temporary error handler to discard errors in silent mode + // phpcs:ignore Squiz.WhiteSpace.ScopeClosingBrace \set_error_handler(static function () {}); try { $result = $flags & STREAM_URL_STAT_LINK @@ -614,134 +508,4 @@ public function url_stat($path, $flags) static::register(); return $result; } - - /** - * Get file resource - * - * @param string $file File path - * @param string $mode The mode used to open the file, as detailed for fopen(). - * @param int $options Holds additional flags set by the streams API. - * @param string $openedPath the full path of the file/resource that was actually opened - * - * @return resource - * @throws \UnexpectedValueException - */ - private function getResource($file, $mode, $options, &$openedPath) - { - $useIncludePath = (bool) ($options & STREAM_USE_PATH); - $args = $this->popNull(array($file, $mode, $useIncludePath, $this->context)); - $resource = \call_user_func_array('fopen', $args); - /* - Determine opened path - */ - if ($resource) { - $meta = \stream_get_meta_data($resource); - if (!isset($meta['uri'])) { - throw new \UnexpectedValueException('Uri not in meta data'); - } - $openedPath = $meta['uri']; - } - return $resource; - } - - /** - * Return a resource with modified content - * - * @param string $file File path - * @param int $options Holds additional flags set by the streams API. - * @param string $openedPath the full path of the file/resource that was actually opened - * - * @return resource - */ - private function getResourceTransformed($file, $options, &$openedPath) - { - $resource = \fopen(static::OUTPUT_DESTINATION, static::OUTPUT_ACCESS_MODE); - $useIncludePath = (bool) ($options & STREAM_USE_PATH); - $args = $this->popNull(array($file, $useIncludePath, $this->context)); - $content = \call_user_func_array('file_get_contents', $args); - $openedPath = $useIncludePath - ? \stream_resolve_include_path($file) - : $file; - if (static::$eventManager) { - $event = static::$eventManager->publish(Debug::EVENT_STREAM_WRAP, $resource, array( - 'content' => $content, - 'filepath' => $file, - )); - if ($event['content'] !== $content) { - self::$filesTransformed[] = $openedPath; - } - $content = $event['content']; - } - \fwrite($resource, $content); - \rewind($resource); - return $resource; - } - - /** - * Check whether this file should be transformed - * - * @param string $file file path - * - * @return bool - */ - private static function isTargeted($file) - { - foreach (static::$pathsExclude as $excludePath) { - if ($file === $excludePath) { - return false; - } - if (\strpos($file, $excludePath . DIRECTORY_SEPARATOR) === 0) { - return false; - } - } - return true; - } - - private function popNull($values) - { - $count = \count($values); - for ($i = $count; $i > 0; $i--) { - if ($values[$i - 1] !== null) { - break; - } - } - return \array_slice($values, 0, $i); - } - - /** - * Register stream wrapper for the specified protocol - * - * First unregisters current protocol - * - * @param string $protocol Protocol such as "file" or "phar" - * - * @return void - * - * @throws \UnexpectedValueException - */ - private static function registerProtocol($protocol) - { - $result = \stream_wrapper_unregister($protocol); - if ($result === false) { - throw new \UnexpectedValueException('Failed to unregister stream wrapper for ' . $protocol); - } - $result = \stream_wrapper_register($protocol, \get_called_class()); - if ($result === false) { - throw new \UnexpectedValueException('Failed to register stream wrapper for ' . $protocol); - } - } - - /** - * Test if file should be transformed - * - * @param string $file Specifies the file/URL that was passed to the original function. - * @param int $options Holds additional flags set by the streams API. - * - * @return bool - */ - private static function shouldTransform($file, $options) - { - $including = (bool) ($options & static::STREAM_OPEN_FOR_INCLUDE); - return static::isTargeted($file) && $including; - } } diff --git a/src/Debug/Utility/FileStreamWrapperBase.php b/src/Debug/Utility/FileStreamWrapperBase.php new file mode 100644 index 00000000..bebd9b8b --- /dev/null +++ b/src/Debug/Utility/FileStreamWrapperBase.php @@ -0,0 +1,262 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.0 + */ + +namespace bdk\Debug\Utility; + +use bdk\Debug; +use bdk\PubSub\Manager; +use UnexpectedValueException; + +/** + * Handle FileStreamWrapper: + * registration + * whether should transform file + * publish Debug::EVENT_STREAM_WRAP + */ +class FileStreamWrapperBase +{ + const OUTPUT_ACCESS_MODE = 'rb+'; + const OUTPUT_DESTINATION = 'php://memory'; + const STREAM_OPEN_FOR_INCLUDE = 128; + + /** @var resource The current context, or null if no context was passed to the caller function */ + public $context; + + public static $filesTransformed = array(); + + protected static $isRegistered = false; + + /** @var string[] */ + protected static $protocols = array('file', 'phar'); + + /** @var Manager */ + private static $eventManager; + + /** @var array paths to exclude from adding tick declaration */ + private static $pathsExclude = array(); + + /** + * Register this stream wrapper + * + * @return void + * + * @throws UnexpectedValueException + */ + public static function register() + { + if (static::$isRegistered) { + return; + } + foreach (static::$protocols as $protocol) { + self::registerProtocol($protocol); + } + static::$isRegistered = true; + /* + Disable OPcache + a) want to make sure we modify required files + b) don't want to cache modified files + */ + \ini_set('opcache.enable', '0'); + } + + /** + * Restore previous wrapper + * + * @return void + * + * @throws UnexpectedValueException + */ + public static function unregister() + { + if (static::$isRegistered === false) { + return; + } + foreach (static::$protocols as $protocol) { + $result = \stream_wrapper_restore($protocol); + if ($result === false) { + throw new UnexpectedValueException('Failed to restore stream wrapper for ' . $protocol); + } + } + static::$isRegistered = false; + } + + /** + * Define EventManager + * + * @param Manager $eventManager Event manager + * + * @return void + */ + public static function setEventManager(Manager $eventManager) + { + static::$eventManager = $eventManager; + } + + /** + * Set paths/directories to exclude + * + * @param string[] $pathsExclude paths/directories to exclude + * + * @return void + */ + public static function setPathsExclude(array $pathsExclude) + { + static::$pathsExclude = \array_unique(\array_map('realpath', $pathsExclude)); + } + + /** + * Get paths/directories to exclude + * + * @return string[] + */ + public static function getPathsExclude() + { + return static::$pathsExclude; + } + + /** + * Get file resource + * + * @param string $file File path + * @param string $mode The mode used to open the file, as detailed for fopen(). + * @param int $options Holds additional flags set by the streams API. + * @param string $openedPath the full path of the file/resource that was actually opened + * + * @return resource + * @throws UnexpectedValueException + */ + protected function getResource($file, $mode, $options, &$openedPath) + { + $useIncludePath = (bool) ($options & STREAM_USE_PATH); + $args = $this->popNull(array($file, $mode, $useIncludePath, $this->context)); + $resource = \call_user_func_array('fopen', $args); + /* + Determine opened path + */ + if ($resource) { + $meta = \stream_get_meta_data($resource); + if (!isset($meta['uri'])) { + throw new UnexpectedValueException('Uri not in meta data'); + } + $openedPath = $meta['uri']; + } + return $resource; + } + + /** + * Return a resource with modified content + * + * @param string $file File path + * @param int $options Holds additional flags set by the streams API. + * @param string $openedPath the full path of the file/resource that was actually opened + * + * @return resource + */ + protected function getResourceTransformed($file, $options, &$openedPath) + { + $resource = \fopen(static::OUTPUT_DESTINATION, static::OUTPUT_ACCESS_MODE); + $useIncludePath = (bool) ($options & STREAM_USE_PATH); + $args = $this->popNull(array($file, $useIncludePath, $this->context)); + $content = \call_user_func_array('file_get_contents', $args); + $openedPath = $useIncludePath + ? \stream_resolve_include_path($file) + : $file; + if (static::$eventManager) { + $event = static::$eventManager->publish(Debug::EVENT_STREAM_WRAP, $resource, array( + 'content' => $content, + 'filepath' => $file, + )); + if ($event['content'] !== $content) { + self::$filesTransformed[] = $openedPath; + } + $content = $event['content']; + } + \fwrite($resource, $content); + \rewind($resource); + return $resource; + } + + /** + * Check whether this file should be transformed + * + * @param string $file file path + * + * @return bool + */ + private static function isTargeted($file) + { + foreach (static::$pathsExclude as $excludePath) { + if ($file === $excludePath) { + return false; + } + if (\strpos($file, $excludePath . DIRECTORY_SEPARATOR) === 0) { + return false; + } + } + return true; + } + + /** + * Remove null values from end of list + * + * @param array $values Values to trim + * + * @return array + */ + protected function popNull($values) + { + $count = \count($values); + for ($i = $count; $i > 0; $i--) { + if ($values[$i - 1] !== null) { + break; + } + } + return \array_slice($values, 0, $i); + } + + /** + * Register stream wrapper for the specified protocol + * + * First unregisters current protocol + * + * @param string $protocol Protocol such as "file" or "phar" + * + * @return void + * + * @throws UnexpectedValueException + */ + private static function registerProtocol($protocol) + { + $result = \stream_wrapper_unregister($protocol); + if ($result === false) { + throw new UnexpectedValueException('Failed to unregister stream wrapper for ' . $protocol); + } + $result = \stream_wrapper_register($protocol, \get_called_class()); + if ($result === false) { + throw new UnexpectedValueException('Failed to register stream wrapper for ' . $protocol); + } + } + + /** + * Test if file should be transformed + * + * @param string $file Specifies the file/URL that was passed to the original function. + * @param int $options Holds additional flags set by the streams API. + * + * @return bool + */ + protected static function shouldTransform($file, $options) + { + $including = (bool) ($options & static::STREAM_OPEN_FOR_INCLUDE); + return $including && static::isTargeted($file); + } +} diff --git a/src/Debug/Utility/FileTree.php b/src/Debug/Utility/FileTree.php index afda4195..37e45f00 100644 --- a/src/Debug/Utility/FileTree.php +++ b/src/Debug/Utility/FileTree.php @@ -6,14 +6,14 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ namespace bdk\Debug\Utility; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; /** * Convert a list of files a tree @@ -39,7 +39,7 @@ public function filesToTree($files, $excludedCounts = array(), $condense = false $dirs[0] = '/' . $dirs[0]; } $node = &$this->getTreeNode($tree, $dirs); - $node[] = new Abstraction(Abstracter::TYPE_STRING, array( + $node[] = new Abstraction(Type::TYPE_STRING, array( 'attribs' => array( 'data-file' => $filepath, ), @@ -73,7 +73,7 @@ private function addExcludedToTree($tree, $excludedCounts) $dirs = array($path); } $node = &$this->getTreeNode($tree, $dirs); - \array_unshift($node, new Abstraction(Abstracter::TYPE_STRING, array( + \array_unshift($node, new Abstraction(Type::TYPE_STRING, array( 'attribs' => array( 'class' => 'exclude-count', ), diff --git a/src/Debug/Utility/Php.php b/src/Debug/Utility/Php.php index 0e04f72b..e26707eb 100644 --- a/src/Debug/Utility/Php.php +++ b/src/Debug/Utility/Php.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -58,7 +58,7 @@ public static function friendlyClassName($mixed) * * @see https://github.com/symfony/polyfill/blob/main/src/Php80/Php80.php */ - public static function getDebugType($val) + public static function getDebugType($val) // phpcs:ignore Generic.Metrics.CyclomaticComplexity { if (PHP_VERSION_ID >= 80000 && \is_array($val) === false && \is_object($val) === false) { return \get_debug_type($val); diff --git a/src/Debug/Utility/PhpDoc.php b/src/Debug/Utility/PhpDoc.php index dc87bd2b..1e77f3ea 100644 --- a/src/Debug/Utility/PhpDoc.php +++ b/src/Debug/Utility/PhpDoc.php @@ -6,127 +6,114 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent - * @version v3.0 + * @copyright 2014-2024 Brad Kent + * @version v3.3 */ namespace bdk\Debug\Utility; +use bdk\Debug\Utility\PhpDoc\Helper; +use bdk\Debug\Utility\PhpDoc\Parsers; +use bdk\Debug\Utility\PhpDoc\Type; use bdk\Debug\Utility\Reflection; use ReflectionMethod; +use Reflector; /** * Get and parse phpDoc block */ -class PhpDoc extends PhpDocBase +class PhpDoc { - public $types = array( - 'array','bool','callable','float','int','iterable','null','object','string', - '$this','self','static', - 'array-key','double','false','mixed','non-empty-array','resource','scalar','true','void', - 'key-of', 'value-of', - 'callable-string', 'class-string', 'literal-string', 'numeric-string', 'non-empty-string', - 'negative-int', 'positive-int', - 'int-mask', 'int-mask-of', - ); + const FULLY_QUALIFY = 1; + const FULLY_QUALIFY_AUTOLOAD = 2; + + /** @var Type */ + public $type; + + protected $className; + protected $fullyQualifyType; /** @var string[] */ protected static $cache = array(); - protected $parsers = array(); + /** @var Reflector */ + protected $reflector; + /** @var Helper */ + protected $helper; + /** @var Parsers */ + protected $parsers; /** * Constructor */ public function __construct() { - $this->setParsers(); + $this->helper = new Helper(); + $this->parsers = new Parsers($this->helper); + $this->type = new Type(); + } + + /** + * Get comment contents + * + * @param Reflector|object|string $what Object, Reflector, className, or doc-block string + * + * @return string + */ + public function getComment($what) + { + $this->reflector = Reflection::getReflector($what, true) ?: null; + $comment = $this->reflector + ? \is_callable(array($this->reflector, 'getDocComment')) + ? $this->reflector->getDocComment() + : '' + : $what; + // remove opening "/**" and closing "*/" + $comment = \preg_replace('#^\s*/\*\*(.+)\*/$#s', '$1', (string) $comment); + // remove leading "*"s + $comment = \preg_replace('#^[ \t]*\*[ ]?#m', '', $comment); + return \trim($comment); } /** * Rudimentary doc-block parsing * * @param string|object|Reflector $what doc-block string, object, or Reflector instance - * @param int $fullyQualifyType Whether to further parse / resolve types + * @param int $fullyQualifyType Whether to fully qualify type(s) + * Bitmask of FULLY_QUALIFY* constants * * @return array */ public function getParsed($what, $fullyQualifyType = 0) { - $this->fullyQualifyType = $fullyQualifyType; - $hash = $this->getHash($what); + $hash = $this->hash($what); if (isset(self::$cache[$hash])) { return self::$cache[$hash]; } $comment = $this->getComment($what); - $this->className = $this->reflector - ? Reflection::classname($this->reflector) - : null; - $parsed = $this->parseComment($comment); - $parsed = \array_merge($this->parseGetDefaults($parsed), $parsed); - $parsed = \array_merge($parsed, $this->replaceInheritDoc($parsed, $comment)); - \ksort($parsed); + $parsed = $this->parse($comment, $this->reflector, $fullyQualifyType); self::$cache[$hash] = $parsed; return $parsed; } /** - * @param string $body tag content - * - * @return string[] - */ - protected static function extractTypeFromBody($body) - { - $type = ''; - $nestingLevel = 0; - for ($i = 0, $iMax = \strlen($body); $i < $iMax; $i++) { - $char = $body[$i]; - if ($nestingLevel === 0 && \trim($char) === '') { - break; - } - $type .= $char; - if (\in_array($char, array('<', '(', '[', '{'), true)) { - $nestingLevel++; - continue; - } - if (\in_array($char, array('>', ')', ']', '}'), true)) { - $nestingLevel--; - continue; - } - } - return array( - 'desc' => \trim(\substr($body, \strlen($type))) ?: null, - 'type' => $type, - ); - } - - /** - * Get parent method/property/etc's parsed docblock + * PhpDoc won't be different between object instances * - * @return array - */ - private function getParentParsed() - { - $parentReflector = Reflection::getParentReflector($this->reflector); - return $this->getParsed($parentReflector, $this->fullyQualifyType); - } - - /** - * Get the parser for the given tag type + * Generate an identifier for what we're parsing * - * @param string $tag phpDoc tag + * @param mixed $what classname, object, or Reflector * - * @return array + * @return string|null */ - private function getTagParser($tag) + public static function hash($what) { - $parser = array(); - foreach ($this->parsers as $parser) { - if (\in_array($tag, $parser['tags'], true)) { - break; - } + if (\is_string($what)) { + return \md5($what); + } + if ($what instanceof Reflector) { + return Reflection::hash($what); } - // if not found, last parser was default - return $parser; + $str = \is_object($what) ? \get_class($what) : \gettype($what); + return \md5($str); } /** @@ -134,42 +121,55 @@ private function getTagParser($tag) * * Comment has already been stripped of comment "*"s * - * @param string $comment comment content + * @param string $comment comment content + * @param Reflector $reflector Reflector instance + * @param int $fullyQualifyType Whether to fully qualify type(s) * * @return array */ - private function parseComment($comment) + private function parse($comment, Reflector $reflector = null, $fullyQualifyType = 0) { - $elementName = $this->reflector ? $this->reflector->getName() : null; + $this->reflector = $reflector; + $this->fullyQualifyType = $fullyQualifyType; + $this->className = $reflector + ? Reflection::classname($reflector) + : null; + $elementName = $reflector + ? $reflector->getName() + : null; $matches = array(); - $parsedTags = array(); + $parsed = array(); if (\preg_match('/^@/m', $comment, $matches, PREG_OFFSET_CAPTURE)) { // we have tags $pos = $matches[0][1]; $strTags = \substr($comment, $pos); - $parsedTags = $this->parseTags($strTags, $elementName); + $parsed = $this->parseTags($strTags, $elementName); // remove tags from comment $comment = $pos > 0 ? \substr($comment, 0, $pos - 1) : ''; } - return \array_merge($parsedTags, $this->parseDescSummary($comment)); + $parsed = \array_merge($parsed, $this->helper->parseDescSummary($comment)); + $parsed = \array_merge($this->parseGetDefaults($parsed), $parsed); + $parsed = \array_merge($parsed, $this->replaceInheritDoc($parsed, $comment)); + \ksort($parsed); + return $parsed; } /** * Get default values * - * @param array $parsedTags Parsed tags + * @param array $parsed Parsed tags * * @return array */ - private function parseGetDefaults(array $parsedTags) + private function parseGetDefaults(array $parsed) { $default = array( 'desc' => null, 'summary' => null, ); - if ($this->reflector instanceof ReflectionMethod || !empty($parsedTags['param'])) { + if ($this->reflector instanceof ReflectionMethod || !empty($parsed['param'])) { $default['return'] = array( 'desc' => null, 'type' => null, @@ -179,30 +179,16 @@ private function parseGetDefaults(array $parsedTags) } /** - * Split description and summary + * Get parent method/property/etc's parsed docblock * - * @param string $comment Beginning of doc comment + * @param Reflector $reflector Reflector instance * - * @return array desc and/or summary (or empty array) + * @return array */ - private function parseDescSummary($comment) + private function parseParent(Reflector $reflector) { - /* - Do some string replacement - */ - $comment = \preg_replace('/^\\\@/m', '@', $comment); - $comment = \str_replace('{@*}', '*/', $comment); - /* - split into summary & description - summary ends with empty whiteline or "." followed by \n - */ - $split = \preg_split('/(\.[\r\n]+|[\r\n]{2})/', $comment, 2, PREG_SPLIT_DELIM_CAPTURE); - $split = \array_replace(array('', '', ''), $split); - // assume that summary and desc won't be "0".. remove empty value and merge - return \array_filter(array( - 'desc' => $this->trimDesc(\trim($split[2])), - 'summary' => \trim($split[0] . $split[1]), // split[1] is the ".\n" - )); + $parentReflector = Reflection::getParentReflector($reflector); + return $this->getParsed($parentReflector, $this->fullyQualifyType); } /** @@ -223,33 +209,55 @@ private function parseDescSummary($comment) */ private function parseTag($tagName, $tagStr = '', $elementName = null) { - $parser = \array_merge(array( - 'callable' => array(), - 'parts' => array(), - 'regex' => null, - ), $this->getTagParser($tagName)); - $parsed = \array_fill_keys($parser['parts'], null); - if (isset($parser['regex'])) { - $matches = array(); - \preg_match($parser['regex'], $tagStr, $matches); - foreach ($parser['parts'] as $part) { - $parsed[$part] = isset($matches[$part]) && $matches[$part] !== '' - ? \trim($matches[$part]) - : null; - } - } + $parser = $this->parsers->getTagParser($tagName); + $parsed = $parser['regex'] + ? $this->parseTagRegex($parser, $tagStr) + : \array_fill_keys($parser['parts'], null); foreach ((array) $parser['callable'] as $callable) { - $parsed = \array_merge($parsed, \call_user_func($callable, $tagStr, $tagName, $parsed, $elementName)); + $parsed = \array_merge($parsed, \call_user_func( + $callable, + $parsed, + array( + 'className' => $this->className, + 'elementName' => $elementName, + 'fullyQualifyType' => $this->fullyQualifyType, + 'phpDoc' => $this, + 'reflector' => $this->reflector, + 'tagName' => $tagName, + 'tagStr' => $tagStr, + ) + )); } - $parsed['desc'] = $this->trimDesc($parsed['desc']); + $parsed['desc'] = $this->helper->trimDesc($parsed['desc']); \ksort($parsed); return $parsed; } + /** + * Parse tag from regex + * + * @param array $parser Parser info (regex & parts) + * @param string $tagStr Raw tag body + * + * @return array + */ + private function parseTagRegex(array $parser, $tagStr) + { + $parsed = array(); + $matches = array(); + \preg_match($parser['regex'], $tagStr, $matches); + foreach ($parser['parts'] as $part) { + $parsed[$part] = isset($matches[$part]) && $matches[$part] !== '' + ? \trim($matches[$part]) + : null; + } + return $parsed; + } + /** * Parse tags * - * @param string $str portion of phpdoc content that contains tags + * @param string $str Portion of phpdoc content that contains tags * @param string $elementName class, property, method, or constant name if available * * @return array @@ -276,25 +284,25 @@ private function parseTags($str, $elementName = null) } /** - * Replace "{@inheritDoc}"" + * Replace "{@inheritDoc}" * * @param array $parsed Parsed PhpDoc comment * @param string $comment raw comment (asterisks removed) * * @return array Parsed PhpDoc comment */ - private function replaceInheritDoc($parsed, $comment) + private function replaceInheritDoc(array $parsed, $comment) { if (!$this->reflector) { return $parsed; } if (\strtolower($comment) === '{@inheritdoc}') { // phpDoc considers this non-standard - return $this->getParentParsed(); + return $this->parseParent($this->reflector); } if (\strtolower($parsed['desc'] . $parsed['summary']) === '{@inheritdoc}') { // phpDoc considers this non-standard - $parentParsed = $this->getParentParsed(); + $parentParsed = $this->parseParent($this->reflector); $parsed['summary'] = $parentParsed['summary']; $parsed['desc'] = $parentParsed['desc']; return $parsed; @@ -305,179 +313,11 @@ private function replaceInheritDoc($parsed, $comment) $parsed['desc'] = \preg_replace_callback( '/{@inheritdoc}/i', function () { - $parentParsed = $this->getParentParsed(); + $parentParsed = $this->parseParent($this->reflector); return $parentParsed['desc']; }, $parsed['desc'] ); return $parsed; } - - /** - * Get the tag parsers - * - * @return void - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @phpcs:disable SlevomatCodingStandard.Functions.FunctionLength.FunctionLength - */ - protected function setParsers() - { - $this->parsers = array( - array( - 'callable' => array( - array($this, 'extractTypeFromBody'), - array($this, 'tagParam'), - ), - 'parts' => array('type','name','desc'), - 'tags' => array('param','property','property-read', 'property-write', 'var'), - ), - array( - // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter - 'callable' => function ($tagStr, $tagName, $parsed) { - $parsed['param'] = $this->parseMethodParams($parsed['param']); - $parsed['static'] = $parsed['static'] !== null; - $parsed['type'] = $this->typeNormalize($parsed['type']); - return $parsed; - }, - 'parts' => array('static', 'type', 'name', 'param', 'desc'), - 'regex' => '/' - . '(?:(?Pstatic)\s+)?' - . '(?:(?P.*?)\s+)?' - . '(?P\S+)' - . '\((?P((?>[^()]+)|(?R))*)\)' // see http://php.net/manual/en/regexp.reference.recursive.php - . '(?:\s+(?P.*))?' - . '/s', - 'tags' => array('method'), - ), - array( - 'callable' => array( - array($this, 'extractTypeFromBody'), - // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter - function ($tagStr, $tagName, $parsed) { - $parsed['type'] = $this->typeNormalize($parsed['type']); - return $parsed; - }, - ), - 'parts' => array('type','desc'), - 'regex' => '/^(?P.*?)' - . '(?:\s+(?P.*))?$/s', - 'tags' => array('return', 'throws'), - ), - array( - 'parts' => array('name', 'email','desc'), - 'regex' => '/^(?P[^<]+)' - . '(?:\s+<(?P\S*)>)?' - . '(?:\s+(?P.*))?' // desc isn't part of the standard - . '$/s', - 'tags' => array('author'), - ), - array( - 'parts' => array('uri', 'desc'), - 'regex' => '/^(?P\S+)' - . '(?:\s+(?P.*))?$/s', - 'tags' => array('link'), - ), - array( - 'parts' => array('uri', 'fqsen', 'desc'), - 'regex' => '/^(?:' - . '(?Phttps?:\/\/\S+)|(?P\S+)' - . ')' - . '(?:\s+(?P.*))?$/s', - 'tags' => array('see'), - ), - array( - // default - 'parts' => array('desc'), - 'regex' => '/^(?P.*?)$/s', - 'tags' => array(), - ), - ); - } - - /** - * Test is string appears to start with a variable name - * - * @param string $str Stringto test - * - * @return bool - */ - private static function strStartsWithVariable($str) - { - if ($str === null) { - return false; - } - return \strpos($str, '$') === 0 - || \strpos($str, '&$') === 0 - || \strpos($str, '...$') === 0 - || \strpos($str, '&...$') === 0; - } - - /** - * clean up parsed tag - * 'param','property','property-read', 'property-write', 'var' - * - * @param string $tagStr phpDoc tag body - * @param string $tagName phpDoc tag name - * @param array $parsed type, name, & desc - * @param string $elementName name of element tag attached to - * - * @return array - * - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter - */ - private function tagParam($tagStr, $tagName, $parsed, $elementName) - { - if (self::strStartsWithVariable($parsed['desc'])) { - \preg_match('/^(\S*)/', $parsed['desc'], $matches); - $parsed['name'] = $matches[1]; - $parsed['desc'] = \preg_replace('/^\S*\s+/', '', $parsed['desc']); - } - if ($tagName !== 'param' && $parsed['name'] !== null) { - $parsed['name'] = \ltrim($parsed['name'], '&$'); - } - if ($tagName === 'param' && $parsed['name'] === null && \strpos($parsed['desc'], ' ') === false) { - $parsed['name'] = $parsed['desc']; - $parsed['desc'] = null; - } - if ($tagName === 'var' && $elementName !== null && $parsed['name'] !== $elementName) { - // name mismatch - $parsed['desc'] = \trim($parsed['name'] . ' ' . $parsed['desc']); - $parsed['name'] = $elementName; - } - $parsed['type'] = $this->typeNormalize($parsed['type']); - return $parsed; - } - - /** - * Trim leading spaces from each description line - * - * @param string $desc string to trim - * - * @return string - */ - private static function trimDesc($desc) - { - $lines = \explode("\n", (string) $desc); - $leadingSpaces = array(); - foreach (\array_filter($lines) as $line) { - $leadingSpaces[] = \strspn($line, ' '); - } - \array_shift($leadingSpaces); // first line will always have zero leading spaces - $trimLen = $leadingSpaces - ? \min($leadingSpaces) - : 0; - if (!$trimLen) { - return $desc; - } - foreach ($lines as $i => $line) { - $lines[$i] = $i > 0 && \strlen($line) - ? \substr($line, $trimLen) - : $line; - } - $desc = \implode("\n", $lines); - return $desc; - } } diff --git a/src/Debug/Utility/PhpDoc/Helper.php b/src/Debug/Utility/PhpDoc/Helper.php new file mode 100644 index 00000000..c70c9559 --- /dev/null +++ b/src/Debug/Utility/PhpDoc/Helper.php @@ -0,0 +1,108 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\Debug\Utility\PhpDoc; + +/** + * PhpDoc parsing helper methods + */ +class Helper +{ + /** + * @param array $parsed Parsed tag info + * @param array $info tagName, raw tag string, etc + * + * @return string[] + */ + public static function extractTypeFromBody(array $parsed, array $info) // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter + { + $tagStr = $info['tagStr']; + $type = ''; + $nestingLevel = 0; + for ($i = 0, $iMax = \strlen($tagStr); $i < $iMax; $i++) { + $char = $tagStr[$i]; + if ($nestingLevel === 0 && \trim($char) === '') { + break; + } + $type .= $char; + if (\in_array($char, array('<', '(', '[', '{'), true)) { + $nestingLevel++; + continue; + } + if (\in_array($char, array('>', ')', ']', '}'), true)) { + $nestingLevel--; + continue; + } + } + return array( + 'desc' => \trim(\substr($tagStr, \strlen($type))) ?: null, + 'type' => $type, + ); + } + + /** + * Split description and summary + * + * @param string $comment Beginning of doc comment + * + * @return array desc and/or summary (or empty array) + */ + public static function parseDescSummary($comment) + { + /* + Do some string replacement + */ + $comment = \preg_replace('/^\\\@/m', '@', $comment); + $comment = \str_replace('{@*}', '*/', $comment); + /* + split into summary & description + summary ends with empty whiteline or "." followed by \n + */ + $split = \preg_split('/(\.[\r\n]+|[\r\n]{2})/', $comment, 2, PREG_SPLIT_DELIM_CAPTURE); + $split = \array_replace(array('', '', ''), $split); + // assume that summary and desc won't be "0".. remove empty value and merge + return \array_filter(array( + 'desc' => self::trimDesc(\trim($split[2])), + 'summary' => \trim($split[0] . $split[1]), // split[1] is the ".\n" + )); + } + + /** + * Trim leading spaces from each description line + * + * @param string $desc string to trim + * + * @return string + */ + public static function trimDesc($desc) + { + $lines = \explode("\n", (string) $desc); + $leadingSpaces = array(); + foreach (\array_filter($lines) as $line) { + $leadingSpaces[] = \strspn($line, ' '); + } + \array_shift($leadingSpaces); // first line will always have zero leading spaces + $trimLen = $leadingSpaces + ? \min($leadingSpaces) + : 0; + if (!$trimLen) { + return $desc; + } + foreach ($lines as $i => $line) { + $lines[$i] = $i > 0 && \strlen($line) + ? \substr($line, $trimLen) + : $line; + } + $desc = \implode("\n", $lines); + return $desc; + } +} diff --git a/src/Debug/Utility/PhpDoc/ParseMethod.php b/src/Debug/Utility/PhpDoc/ParseMethod.php new file mode 100644 index 00000000..f756c063 --- /dev/null +++ b/src/Debug/Utility/PhpDoc/ParseMethod.php @@ -0,0 +1,117 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\Debug\Utility\PhpDoc; + +/** + * Parse 'method' tag (magic methods) + */ +class ParseMethod +{ + protected $helper; + + /** + * Constructor + * + * @param PhpDocHelper $helper Helper instance + */ + public function __construct(Helper $helper) + { + $this->helper = $helper; + } + + /** + * Parse @method tag + * + * @param array $parsed type, name, & desc + * @param array $info tagName, raw tag string, etc + * + * @return array + */ + public function __invoke(array $parsed, array $info) + { + $phpDoc = $info['phpDoc']; + $parsed['param'] = $this->parseMethodParams($parsed['param'], $info); + $parsed['static'] = $parsed['static'] !== null; + $parsed['type'] = $phpDoc->type->normalize($parsed['type'], $info['className'], $info['fullyQualifyType']); + return $parsed; + } + + /** + * Parse @method parameters + * + * @param string $paramStr parameter string + * @param array $info tagName, raw tag string, etc + * + * @return array + */ + protected function parseMethodParams($paramStr, array $info) + { + $params = $paramStr + ? self::paramsSplit($paramStr) + : array(); + $phpDoc = $info['phpDoc']; + $matches = array(); + foreach ($params as $i => $str) { + \preg_match('/^(?:([^=]*?)\s)?([^\s=]+)(?:\s*=\s*(\S+))?$/', $str, $matches); + $matches = \array_replace(array('?', null), $matches); + $name = $matches[2]; + $paramInfo = array( + 'isVariadic' => \strpos($name, '...') !== false, + 'name' => \trim($name, '&$,.'), + 'type' => $phpDoc->type->normalize($matches[1], $info['className'], $info['fullyQualifyType']), + ); + if (!empty($matches[3])) { + $paramInfo['defaultValue'] = $matches[3]; + } + \ksort($paramInfo); + $params[$i] = $paramInfo; + } + return $params; + } + + /** + * Split @method parameter string into individual params + * + * @param string $paramStr parameter string + * + * @return string[] + */ + private static function paramsSplit($paramStr) + { + $chars = \str_split($paramStr); + $depth = 0; + $params = array(); + $pos = 0; + $startPos = 0; + foreach ($chars as $pos => $char) { + switch ($char) { + case ',': + if ($depth === 0) { + $params[] = \trim(\substr($paramStr, $startPos, $pos - $startPos)); + $startPos = $pos + 1; + } + break; + case '[': + case '(': + $depth++; + break; + case ']': + case ')': + $depth--; + break; + } + } + $params[] = \trim(\substr($paramStr, $startPos, $pos + 1 - $startPos)); + return $params; + } +} diff --git a/src/Debug/Utility/PhpDoc/ParseParam.php b/src/Debug/Utility/PhpDoc/ParseParam.php new file mode 100644 index 00000000..f175203a --- /dev/null +++ b/src/Debug/Utility/PhpDoc/ParseParam.php @@ -0,0 +1,84 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\Debug\Utility\PhpDoc; + +/** + * Parse 'param', 'property', 'property-read', 'property-write', & 'var' + */ +class ParseParam +{ + protected $helper; + + /** + * Constructor + * + * @param PhpDocHelper $helper Helper instance + */ + public function __construct(Helper $helper) + { + $this->helper = $helper; + } + + /** + * Parse @method tag + * + * @param array $parsed type, name, & desc + * @param array $info tagName, raw tag string, etc + * + * @return array + */ + public function __invoke(array $parsed, array $info) + { + $tagName = $info['tagName']; + if (self::strStartsWithVariable($parsed['desc'])) { + \preg_match('/^(\S*)/', $parsed['desc'], $matches); + $parsed['name'] = $matches[1]; + $parsed['desc'] = \preg_replace('/^\S*\s+/', '', $parsed['desc']); + } + if ($tagName === 'param' && $parsed['name'] === null && \strpos($parsed['desc'], ' ') === false) { + $parsed['name'] = $parsed['desc']; + $parsed['desc'] = null; + } + if ($tagName === 'param') { + $parsed['isVariadic'] = \strpos((string) $parsed['name'], '...') !== false; + } + if ($parsed['name']) { + $parsed['name'] = \trim($parsed['name'], '&$,.'); + } + if ($tagName === 'var' && $info['elementName'] !== null && $parsed['name'] !== $info['elementName']) { + // name mismatch + $parsed['desc'] = \trim($parsed['name'] . ' ' . $parsed['desc']); + $parsed['name'] = $info['elementName']; + } + $parsed['type'] = $info['phpDoc']->type->normalize($parsed['type'], $info['className'], $info['fullyQualifyType']); + return $parsed; + } + + /** + * Test is string appears to start with a variable name + * + * @param string $str Stringto test + * + * @return bool + */ + private static function strStartsWithVariable($str) + { + if ($str === null) { + return false; + } + return \strpos($str, '$') === 0 + || \strpos($str, '&$') === 0 + || \strpos($str, '...$') === 0 + || \strpos($str, '&...$') === 0; + } +} diff --git a/src/Debug/Utility/PhpDoc/Parsers.php b/src/Debug/Utility/PhpDoc/Parsers.php new file mode 100644 index 00000000..9016359b --- /dev/null +++ b/src/Debug/Utility/PhpDoc/Parsers.php @@ -0,0 +1,134 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\Debug\Utility\PhpDoc; + +/** + * PhpDoc parsing helper methods + */ +class Parsers +{ + protected $helper; + protected $parsers = array(); + protected $parseMethod; + protected $parseParam; + + /** + * Constructor + * + * @param Helper $helper Helper instance + */ + public function __construct(Helper $helper) + { + $this->helper = $helper; + $this->parseMethod = new ParseMethod($helper); + $this->parseParam = new ParseParam($helper); + $this->setParsers(); + } + + /** + * Get the parser for the given tag type + * + * @param string $tag phpDoc tag + * + * @return array + */ + public function getTagParser($tag) + { + $parser = array(); + foreach ($this->parsers as $parser) { + if (\in_array($tag, $parser['tags'], true)) { + break; + } + } + // if not found, last parser was default + return \array_merge(array( + 'callable' => array(), + 'parts' => array(), + 'regex' => null, + ), $parser); + } + + /** + * Get the tag parsers + * + * @return void + */ + protected function setParsers() // phpcs:ignore SlevomatCodingStandard.Functions.FunctionLength.FunctionLength + { + $this->parsers = array( + array( + 'callable' => array( + array($this->helper, 'extractTypeFromBody'), + $this->parseParam, + ), + 'parts' => array('type', 'name', 'desc'), + 'tags' => array('param', 'property', 'property-read', 'property-write', 'var'), + ), + array( + 'callable' => array( + $this->parseMethod, + ), + 'parts' => array('static', 'type', 'name', 'param', 'desc'), + 'regex' => '/' + . '(?:(?Pstatic)\s+)?' + . '(?:(?P.*?)\s+)?' + . '(?P\S+)' + . '\((?P((?>[^()]+)|(?R))*)\)' // see http://php.net/manual/en/regexp.reference.recursive.php + . '(?:\s+(?P.*))?' + . '/s', + 'tags' => array('method'), + ), + array( + 'callable' => array( + array($this->helper, 'extractTypeFromBody'), + static function (array $parsed, array $info) { + $parsed['type'] = $info['phpDoc']->type->normalize($parsed['type'], $info['className'], $info['fullyQualifyType']); + return $parsed; + }, + ), + 'parts' => array('type', 'desc'), + 'regex' => '/^(?P.*?)' + . '(?:\s+(?P.*))?$/s', + 'tags' => array('return', 'throws'), + ), + array( + 'parts' => array('name', 'email', 'desc'), + 'regex' => '/^(?P[^<]+)' + . '(?:\s+<(?P\S*)>)?' + . '(?:\s+(?P.*))?' // desc isn't part of the standard + . '$/s', + 'tags' => array('author'), + ), + array( + 'parts' => array('uri', 'desc'), + 'regex' => '/^(?P\S+)' + . '(?:\s+(?P.*))?$/s', + 'tags' => array('link'), + ), + array( + 'parts' => array('uri', 'fqsen', 'desc'), + 'regex' => '/^(?:' + . '(?Phttps?:\/\/\S+)|(?P\S+)' + . ')' + . '(?:\s+(?P.*))?$/s', + 'tags' => array('see'), + ), + array( + // default + 'parts' => array('desc'), + 'regex' => '/^(?P.*?)$/s', + 'tags' => array(), + ), + ); + } +} diff --git a/src/Debug/Utility/PhpDoc/Type.php b/src/Debug/Utility/PhpDoc/Type.php new file mode 100644 index 00000000..e058d011 --- /dev/null +++ b/src/Debug/Utility/PhpDoc/Type.php @@ -0,0 +1,130 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\Debug\Utility\PhpDoc; + +use bdk\Debug\Utility\PhpDoc; +use bdk\Debug\Utility\Reflection; +use bdk\Debug\Utility\UseStatements; + +/** + * PhpDoc parsing helper methods + */ +class Type +{ + public $types = array( + 'array','bool','callable','float','int','iterable','null','object','string', + '$this','self','static', + 'array-key','double','false','mixed','non-empty-array','resource','scalar','true','void', + 'key-of', 'value-of', + 'callable-string', 'class-string', 'literal-string', 'numeric-string', 'non-empty-string', + 'negative-int', 'positive-int', + 'int-mask', 'int-mask-of', + ); + + /** + * Convert "self[]|null" to array + * + * @param string $type type hint + * @param string $className Classname where element is defined + * @param int $fullyQualifyType Whether to fully qualify type(s) + * Bitmask of FULLY_QUALIFY* constants + * + * @return string|null + */ + public function normalize($type, $className, $fullyQualifyType = 0) + { + if (\in_array($type, array('', null), true)) { + return null; + } + if (\preg_match('/array[<([{]/', $type)) { + // type contains "complex" array type... don't deal with parsing + return $type; + } + $types = \preg_split('#\s*\|\s*#', $type); + foreach ($types as $i => $type) { + $types[$i] = $this->normalizeSingle($type, $className, $fullyQualifyType); + } + return \implode('|', $types); + } + + /** + * Normalize individual part of type + * + * @param string $type type hint + * @param string $className Classname where element is defined + * @param int $fullyQualifyType Whether to fully qualify type(s) + * Bitmask of FULLY_QUALIFY* constants + * + * @return string + */ + private function normalizeSingle($type, $className, $fullyQualifyType = 0) + { + if (\strpos($type, '\\') === 0) { + return \substr($type, 1); + } + $isArray = false; + if (\substr($type, -2) === '[]') { + $isArray = true; + $type = \substr($type, 0, -2); + } + $translate = array( + 'boolean' => 'bool', + 'integer' => 'int', + 'self' => $className, + ); + if (isset($translate[$type])) { + $type = $translate[$type]; + } elseif ($fullyQualifyType && \in_array($type, $this->types, true) === false) { + $type = $this->resolveTypeClass($type, $className, $fullyQualifyType); + } + if ($isArray) { + $type .= '[]'; + } + return $type; + } + + /** + * Check type-hint in use statements, and whether relative or absolute + * + * @param string $type Type-hint + * @param string $className Classname where element is defined + * @param int $fullyQualifyType Whether to fully qualify type(s) + * Bitmask of FULLY_QUALIFY* constants + * + * @return string + */ + private function resolveTypeClass($type, $className, $fullyQualifyType = 0) + { + $first = \substr($type, 0, \strpos($type, '\\') ?: 0) ?: $type; + $className = $className ?: ''; + $classReflector = Reflection::getReflector($className, true); + $useStatements = $classReflector + ? UseStatements::getUseStatements($classReflector)['class'] + : array(); + if (isset($useStatements[$first])) { + return $useStatements[$first] . \substr($type, \strlen($first)); + } + $namespace = \substr($className, 0, \strrpos($className, '\\') ?: 0); + if (!$namespace) { + return $type; + } + /* + Truly relative? Or, does PhpDoc omit '\' ? + Not 100% accurate, but check if assumed namespace'd class exists + */ + $autoload = ($fullyQualifyType & PhpDoc::FULLY_QUALIFY_AUTOLOAD) === PhpDoc::FULLY_QUALIFY_AUTOLOAD; + return \class_exists($namespace . '\\' . $type, $autoload) + ? $namespace . '\\' . $type + : $type; + } +} diff --git a/src/Debug/Utility/PhpDocBase.php b/src/Debug/Utility/PhpDocBase.php deleted file mode 100644 index 560cf385..00000000 --- a/src/Debug/Utility/PhpDocBase.php +++ /dev/null @@ -1,228 +0,0 @@ - - * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent - * @version v3.0 - */ - -namespace bdk\Debug\Utility; - -use bdk\Debug\Utility\Reflection; -use bdk\Debug\Utility\UseStatements; -use Reflector; - -/** - * Get unparsed PhpDoc comment - */ -class PhpDocBase -{ - const FULLY_QUALIFY = 1; - const FULLY_QUALIFY_AUTOLOAD = 2; - - /** @var Reflector */ - protected $reflector; - - protected $className = null; - protected $fullyQualifyType = 0; - - /** - * Get comment contents - * - * @param Reflector|object|string $what Object, Reflector, className, or doc-block string - * - * @return string - */ - protected function getComment($what) - { - $this->reflector = Reflection::getReflector($what, true); - $docComment = $this->reflector - ? \is_callable(array($this->reflector, 'getDocComment')) - ? $this->reflector->getDocComment() - : '' - : $what; - // remove opening "/**" and closing "*/" - $docComment = \preg_replace('#^\s*/\*\*(.+)\*/$#s', '$1', (string) $docComment); - // remove leading "*"s - $docComment = \preg_replace('#^[ \t]*\*[ ]?#m', '', $docComment); - return \trim($docComment); - } - - /** - * PhpDoc won't be different between object instances - * - * Generate an identifier for what we're parsing - * - * @param mixed $what classname, object, or Reflector - * - * @return string|null - */ - protected static function getHash($what) - { - if (\is_string($what)) { - return \md5($what); - } - if ($what instanceof Reflector) { - return Reflection::hash($what); - } - $str = \is_object($what) ? \get_class($what) : \gettype($what); - return \md5($str); - } - - /** - * Parse @method parameters - * - * @param string $paramStr parameter string - * - * @return array - */ - protected function parseMethodParams($paramStr) - { - $params = $paramStr - ? self::paramsSplit($paramStr) - : array(); - $matches = array(); - foreach ($params as $i => $str) { - \preg_match('/^(?:([^=]*?)\s)?([^\s=]+)(?:\s*=\s*(\S+))?$/', $str, $matches); - $matches = \array_replace(array('?', null), $matches); - $info = array( - 'name' => $matches[2], - 'type' => $this->typeNormalize($matches[1]), - ); - if (!empty($matches[3])) { - $info['defaultValue'] = $matches[3]; - } - \ksort($info); - $params[$i] = $info; - } - return $params; - } - - /** - * Convert "self[]|null" to array - * - * @param string $type type hint - * - * @return string|null - */ - protected function typeNormalize($type) - { - if (\in_array($type, array('', null), true)) { - return null; - } - if (\preg_match('/array[<([{]/', $type)) { - // type contains "complex" array type... don't deal with parsing - return $type; - } - $types = \preg_split('#\s*\|\s*#', $type); - foreach ($types as $i => $type) { - $types[$i] = $this->typeNormalizeSingle($type); - } - return \implode('|', $types); - } - - /** - * Normalize individual part of type - * - * @param string $type type hint - * - * @return string - */ - private function typeNormalizeSingle($type) - { - if (\strpos($type, '\\') === 0) { - return \substr($type, 1); - } - $isArray = false; - if (\substr($type, -2) === '[]') { - $isArray = true; - $type = \substr($type, 0, -2); - } - $translate = array( - 'boolean' => 'bool', - 'integer' => 'int', - 'self' => $this->className, - ); - if (isset($translate[$type])) { - $type = $translate[$type]; - } elseif ($this->fullyQualifyType && \in_array($type, $this->types, true) === false) { - $type = $this->resolvePhpDocTypeClass($type); - } - if ($isArray) { - $type .= '[]'; - } - return $type; - } - - /** - * Split @method parameter string into individual params - * - * @param string $paramStr parameter string - * - * @return string[] - */ - private static function paramsSplit($paramStr) - { - $chars = \str_split($paramStr); - $depth = 0; - $params = array(); - $pos = 0; - $startPos = 0; - foreach ($chars as $pos => $char) { - switch ($char) { - case ',': - if ($depth === 0) { - $params[] = \trim(\substr($paramStr, $startPos, $pos - $startPos)); - $startPos = $pos + 1; - } - break; - case '[': - case '(': - $depth++; - break; - case ']': - case ')': - $depth--; - break; - } - } - $params[] = \trim(\substr($paramStr, $startPos, $pos + 1 - $startPos)); - return $params; - } - - /** - * Check type-hint in use statements, and whether relative or absolute - * - * @param string $type Type-hint - * - * @return string - */ - private function resolvePhpDocTypeClass($type) - { - $first = \substr($type, 0, \strpos($type, '\\') ?: 0) ?: $type; - $className = $this->className ?: ''; - $classReflector = Reflection::getReflector($className, true); - $useStatements = $classReflector - ? UseStatements::getUseStatements($classReflector)['class'] - : array(); - if (isset($useStatements[$first])) { - return $useStatements[$first] . \substr($type, \strlen($first)); - } - $namespace = \substr($className, 0, \strrpos($className, '\\') ?: 0); - if (!$namespace) { - return $type; - } - /* - Truly relative? Or, does PhpDoc omit '\' ? - Not 100% accurate, but check if assumed namespace'd class exists - */ - $autoload = ($this->fullyQualifyType & self::FULLY_QUALIFY_AUTOLOAD) === self::FULLY_QUALIFY_AUTOLOAD; - return \class_exists($namespace . '\\' . $type, $autoload) - ? $namespace . '\\' . $type - : $type; - } -} diff --git a/src/Debug/Utility/Profile.php b/src/Debug/Utility/Profile.php index 93273af4..9b9799e4 100644 --- a/src/Debug/Utility/Profile.php +++ b/src/Debug/Utility/Profile.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -157,7 +157,7 @@ protected function popStack() $this->data[$funcPopped]['ownTime'] += $timeElapsed - $stackInfo['subTime']; $this->data[$funcPopped]['totalTime'] += $timeElapsed; if ($this->data[$funcPopped]['calls'] === 0) { - $this->data[$funcPopped]['calls'] ++; + $this->data[$funcPopped]['calls']++; } if ($this->funcStack) { $this->funcStack[\count($this->funcStack) - 1]['subTime'] += $timeElapsed; @@ -187,7 +187,7 @@ protected function pushStack($funcName) 'ownTime' => 0, // time spent in function excluding nested funcs ); } - $this->data[$funcName]['calls'] ++; + $this->data[$funcName]['calls']++; } /** diff --git a/src/Debug/Utility/SerializeLog.php b/src/Debug/Utility/SerializeLog.php index d9f90cb0..5b4b1892 100644 --- a/src/Debug/Utility/SerializeLog.php +++ b/src/Debug/Utility/SerializeLog.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -19,6 +19,7 @@ use bdk\Debug\Abstraction\Object\Abstraction as ObjectAbstraction; use bdk\Debug\Abstraction\Object\Methods; use bdk\Debug\Abstraction\Object\Properties; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; use bdk\Debug\Utility\Php; use bdk\Debug\Utility\StringUtil; @@ -194,7 +195,7 @@ private static function importLegacy(array $vals) // we are an abstraction $type = $val['type']; unset($val['debug'], $val['type']); - if ($type !== Abstracter::TYPE_OBJECT) { + if ($type !== Type::TYPE_OBJECT) { return new Abstraction($type, $val); } $val['properties'] = self::importLegacy($val['properties']); diff --git a/src/Debug/Utility/StringUtil.php b/src/Debug/Utility/StringUtil.php index 072952c8..a9d67050 100644 --- a/src/Debug/Utility/StringUtil.php +++ b/src/Debug/Utility/StringUtil.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ @@ -15,9 +15,9 @@ use bdk\Debug\Utility; use bdk\Debug\Utility\ArrayUtil; use bdk\Debug\Utility\Php; -use bdk\Debug\Utility\Utf8; use bdk\HttpMessage\Utility\ContentType; use DOMDocument; +use finfo; use InvalidArgumentException; use Psr\Http\Message\StreamInterface; use SqlFormatter; @@ -92,7 +92,7 @@ public static function contentType($val) if ($val instanceof StreamInterface) { $val = Utility::getStreamContents($val); } - $finfo = new \finfo(FILEINFO_MIME_TYPE); + $finfo = new finfo(FILEINFO_MIME_TYPE); $contentType = $finfo->buffer($val); if ($contentType !== ContentType::TXT) { return $contentType; @@ -531,7 +531,7 @@ private static function interpolateValue($placeholder) $val = ArrayUtil::pathGet($val, \array_slice($path, 1), $noValue); } if ($val === $noValue) { - return null; + return null; // will not replace token } if ($val === null) { return ''; diff --git a/src/Debug/Utility/Table.php b/src/Debug/Utility/Table.php index a8dc4855..c83dce25 100644 --- a/src/Debug/Utility/Table.php +++ b/src/Debug/Utility/Table.php @@ -6,14 +6,14 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.1 */ namespace bdk\Debug\Utility; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Utility\TableRow; /** @@ -268,7 +268,7 @@ private function processRowsGet($rows) $rows = $this->preCrate($rows); } $rows = $this->debug->abstracter->crate($rows, 'table'); - if ($this->debug->abstracter->isAbstraction($rows, Abstracter::TYPE_OBJECT)) { + if ($this->debug->abstracter->isAbstraction($rows, Type::TYPE_OBJECT)) { $this->meta['tableInfo']['class'] = $rows['className']; $this->meta['tableInfo']['summary'] = $rows['phpDoc']['summary']; $rows = $rows['traverseValues'] diff --git a/src/Debug/Utility/TableRow.php b/src/Debug/Utility/TableRow.php index c710dfb5..28db4238 100644 --- a/src/Debug/Utility/TableRow.php +++ b/src/Debug/Utility/TableRow.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -14,6 +14,7 @@ use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; /** * Represent a table row @@ -136,7 +137,7 @@ public function keyValues($keys) */ private function valuesAbs(Abstraction $abs) { - if ($abs['type'] !== Abstracter::TYPE_OBJECT) { + if ($abs['type'] !== Type::TYPE_OBJECT) { // resource & callable $this->info['isScalar'] = true; return array(self::SCALAR => $abs); diff --git a/src/Debug/Utility/UseStatements.php b/src/Debug/Utility/UseStatements.php index 9c40a9ad..f29fc916 100644 --- a/src/Debug/Utility/UseStatements.php +++ b/src/Debug/Utility/UseStatements.php @@ -183,7 +183,7 @@ private static function recordToken($token) * * @return void */ - private static function recordTokenArray($token) + private static function recordTokenArray($token) // phpcs:ignore Generic.Metrics.CyclomaticComplexity { switch (self::$record) { case 'namespace': diff --git a/src/Debug/Utility/Utf8Buffer.php b/src/Debug/Utility/Utf8Buffer.php index ca829453..027aefab 100644 --- a/src/Debug/Utility/Utf8Buffer.php +++ b/src/Debug/Utility/Utf8Buffer.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -96,7 +96,7 @@ public function analyze() $curBlockType = 'utf8'; // utf8, utf8special, other $curBlockStart = 0; // string offset while ($this->curI < $this->stats['strlen']) { - $this->stats['mbStrlen'] ++; + $this->stats['mbStrlen']++; $curI = $this->curI; $charType = $this->getOffsetCharType(); if ($charType !== $curBlockType) { diff --git a/src/Debug/Utility/Utility.php b/src/Debug/Utility/Utility.php index 7f71a6c2..e6e536d4 100644 --- a/src/Debug/Utility/Utility.php +++ b/src/Debug/Utility/Utility.php @@ -6,7 +6,7 @@ * @package PHPDebugConsole * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2022 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 */ @@ -254,6 +254,7 @@ public static function isFile($val) * @param string|array $value Header value(s) * * @return void + * * @phpcs:disable SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions.NonFullyQualified */ private static function emitHeader($name, $value) diff --git a/src/ErrorHandler/AbstractComponent.php b/src/ErrorHandler/AbstractComponent.php index 95596253..727ada9e 100644 --- a/src/ErrorHandler/AbstractComponent.php +++ b/src/ErrorHandler/AbstractComponent.php @@ -4,7 +4,7 @@ * @package bdk\ErrorHandler * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.3 */ @@ -124,8 +124,6 @@ public function setCfg($mixed, $val = null) * @param array $prev previous config values * * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function postSetCfg($cfg = array(), $prev = array()) { diff --git a/src/ErrorHandler/AbstractError.php b/src/ErrorHandler/AbstractError.php new file mode 100644 index 00000000..739e56cc --- /dev/null +++ b/src/ErrorHandler/AbstractError.php @@ -0,0 +1,343 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @copyright 2014-2024 Brad Kent + * @version v3.3 + */ + +namespace bdk\ErrorHandler; + +use bdk\PubSub\Event; +use InvalidArgumentException; + +/** + * Error object + * + * @property array $context lines surrounding error + * @property array $trace backtrace + */ +class AbstractError extends Event +{ + const CAT_DEPRECATED = 'deprecated'; + const CAT_ERROR = 'error'; + const CAT_NOTICE = 'notice'; + const CAT_STRICT = 'strict'; + const CAT_WARNING = 'warning'; + const CAT_FATAL = 'fatal'; + + protected static $errCategories = array( + self::CAT_DEPRECATED => array( E_DEPRECATED, E_USER_DEPRECATED ), + self::CAT_ERROR => array( E_USER_ERROR, E_RECOVERABLE_ERROR ), + self::CAT_FATAL => array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_CORE_ERROR ), + self::CAT_NOTICE => array( E_NOTICE, E_USER_NOTICE ), + self::CAT_STRICT => array( E_STRICT ), + self::CAT_WARNING => array( E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING ), + ); + protected static $errTypes = array( + E_ALL => 'E_ALL', // listed here for completeness + E_COMPILE_ERROR => 'Compile Error', // handled via shutdown function + E_COMPILE_WARNING => 'Compile Warning', // handled? + E_CORE_ERROR => 'Core Error', // handled via shutdown function + E_CORE_WARNING => 'Core Warning', // handled? + E_DEPRECATED => 'Deprecated', // php 5.3 : 8192 + E_ERROR => 'Fatal Error', // handled via shutdown function + E_NOTICE => 'Notice', + E_PARSE => 'Parsing Error', // handled via shutdown function + E_RECOVERABLE_ERROR => 'Recoverable Error', // php 5.2 : 4096 + E_STRICT => 'Strict', // php 5.0 : 2048 + E_USER_DEPRECATED => 'User Deprecated', // php 5.3 : 16384 + E_USER_ERROR => 'User Error', + E_USER_NOTICE => 'User Notice', + E_USER_WARNING => 'User Warning', + E_WARNING => 'Warning', + ); + protected static $userErrors = array( + E_USER_DEPRECATED, + E_USER_ERROR, + E_USER_NOTICE, + E_USER_WARNING, + ); + + /** + * Store fatal non-Exception backtrace + * + * Initially null, will become array or false on attempt to get backtrace + * + * @var array|false|null + */ + protected $backtrace = null; + + /** + * @var array Array of key/values + */ + protected $values = array( // phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder + 'type' => null, // int: The severity / level / one of the E_* constants + 'message' => '', // The raw error message + 'evalLine' => null, + 'file' => null, // Filepath the error was raised in + 'line' => null, // Line the error was raised in + 'vars' => array(), // Active symbol table at point error occured + 'category' => null, + 'continueToNormal' => null, // let PHP do its thing (log error / exit if E_USER_ERROR) + 'continueToPrevHandler' => true, + 'exception' => null, + 'hash' => null, + 'isFirstOccur' => true, // per error (ie a error inside a loop, or inside a functon called multiple times) + 'isHtml' => false, + 'isSuppressed' => false, + 'throw' => false, // whether to throw as exception (fatal errors never throw) + 'typeStr' => '', // friendly version of 'type' + ); + + /** + * {@inheritDoc} + */ + public function setValues(array $values = array()) + { + $this->assertValues($values); + if (!empty($this->values['constructor'])) { + $this->setValuesInit($values); + } + $values = \array_merge($this->values, $values); + parent::setValues($values); + } + + /** + * Get human-friendly error type + * + * @param int $type E_xx constant value + * + * @return string + */ + public static function typeStr($type) + { + return isset(self::$errTypes[$type]) + ? self::$errTypes[$type] + : ''; + } + + /** + * Validate error values + * + * @param array $values Initial error values + * + * @return void + * + * @throws InvalidArgumentException + */ + private function assertValues($values) + { + $values = \array_merge($this->values, $values); + $keysMustHave = array('type', 'message', 'file', 'line'); + $keys = \array_keys(\array_filter($values)); + if (\array_intersect($keysMustHave, $keys) !== $keysMustHave) { + throw new InvalidArgumentException('Error values must include: type, message, file, & line'); + } + $validTypes = \array_diff_key(self::$errTypes, \array_flip(array(E_ALL))); + if (\array_key_exists($values['type'], $validTypes) === false) { + throw new InvalidArgumentException('invalid error type specified'); + } + if (\array_key_exists('vars', $values) && \is_array($values['vars']) === false) { + throw new InvalidArgumentException('Error vars must be an array'); + } + } + + /** + * {@inheritDoc} + */ + protected function onSet($values = array()) + { + $this->values['category'] = $this->valCategory(); + $this->values['isHtml'] = $this->valIsHtml(); + $this->values['typeStr'] = $this->typeStr($this->values['type']); + unset($this->values['vars']['GLOBALS']); + if (isset($values['message'])) { + $this->values['message'] = $this->onSetMessage($values['message']); + } + $regexEvaldCode = '/^(.+)\((\d+)\) : eval\(\)\'d code$/'; + $matches = array(); + if (\preg_match($regexEvaldCode, (string) $this->values['file'], $matches)) { + // reported line = line within eval + // line inside paren is the line `eval` is on + $this->values['evalLine'] = $this->values['line']; + $this->values['file'] = $matches[1]; + $this->values['line'] = (int) $matches[2]; + } + if ($this->backtrace === null && \in_array($this->values['type'], array(E_ERROR, E_USER_ERROR), true) && $this->values['exception'] === null) { + // will return empty unless xdebug extension installed/enabled + $this->backtrace = $this->subject->backtrace->get(); + } + } + + /** + * Check for anonymous class notation + * Replace with more usefull parent class + * + * @param string $message Error Message + * + * @return string + */ + private function onSetMessage($message) + { + $regex = '/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00(.*?)(?:0x?|:([0-9]++)\$)[0-9a-fA-F]++/'; + return \preg_replace_callback($regex, static function ($matches) { + $friendlyClassName = \get_parent_class($matches[0]) ?: \key(\class_implements($matches[0], false)) ?: 'class'; + return $friendlyClassName . '@anonymous'; + }, $message); + } + + /** + * Set the core values (type, message, file, line) + * + * @param array $values values being set + * + * @return void + */ + private function setValuesInit($values) + { + $this->values = \array_merge( + $this->values, + \array_intersect_key($values, \array_flip(array('type', 'message', 'file', 'line'))) + ); + $this->setValuesInitDefault(); + $this->values = \array_merge($this->values, $values); + if ($this->isFatal()) { + $count = 0; + // fatal message may contain trace info... + // this occurs if fatal encountered in shutdown + $this->values['message'] = \preg_replace( + '/ in \S+\nStack trace:\n(#\d+ .+\n)+ thrown/', + '', + (string) $this->values['message'], + -1, + $count + ); + if ($count) { + // don't try to get trace info. + $this->backtrace = array(); + $this->values['exception'] = null; + } + } + } + + /** + * Set values that will remain unchainged after __construct + * + * @return void + */ + private function setValuesInitDefault() + { + $errType = $this->values['type']; + $prevOccurrence = $this->subject->get('error', $this->values['hash']); + $isSuppressed = $this->valIsSuppressed(); + $this->values['hash'] = $this->valHash(); + $this->values['category'] = $this->valCategory(); + $this->values = \array_merge($this->values, array( + 'continueToNormal' => $this->valContinueToNormal($isSuppressed), + 'continueToPrevHandler' => $this->subject->getCfg('continueToPrevHandler'), + 'exception' => $this->subject->get('uncaughtException'), // non-null if error is uncaught-exception + 'isFirstOccur' => $prevOccurrence === null, + 'isSuppressed' => $isSuppressed, + 'throw' => $this->isFatal() === false && ($errType & $this->subject->getCfg('errorThrow')) === $errType, + )); + } + + /** + * Get error "category" + * + * @return string|null + */ + private function valCategory() + { + $return = null; + foreach (self::$errCategories as $category => $errTypes) { + if (\in_array($this->values['type'], $errTypes, true)) { + $return = $category; + break; + } + } + return $return; + } + + /** + * get default continueToNormal flag + * + * @param bool $isSuppressed Whether error is suppressed + * + * @return bool + */ + private function valContinueToNormal($isSuppressed) + { + $prevOccurrence = $this->subject->get('error', $this->values['hash']); + $continueToNormal = $isSuppressed === false && $prevOccurrence === null; + if ($continueToNormal === false || $this->values['category'] !== self::CAT_ERROR) { + return $continueToNormal; + } + // we are a user error + switch ($this->subject->getCfg('onEUserError')) { + case 'continue': + return false; + case 'log': + return false; + case 'normal': + return true; + } + return $continueToNormal; + } + + /** + * Generate hash used to uniquely identify this error + * + * @return string hash + */ + private function valHash() + { + $errMsg = $this->values['message']; + // (\(.*?)\d+(.*?\)) "(tried to allocate 16384 bytes)" -> "(tried to allocate xxx bytes)" + $errMsg = \preg_replace('/(\(.*?)\d+(.*?\))/', '\1x\2', $errMsg); + // "blah123" -> "blahxxx" + $errMsg = \preg_replace('/\b([a-z]+\d+)+\b/', 'xxx', $errMsg); + // "-123.123" -> "xxx" + $errMsg = \preg_replace('/\b[\d.-]{4,}\b/', 'xxx', $errMsg); + // remove "comments".. this allows throttling email, while still adding unique info to user errors + $errMsg = \preg_replace('/\s*##.+$/', '', $errMsg); + return \md5($this->values['file'] . $this->values['line'] . $this->values['type'] . $errMsg); + } + + /** + * isHtml? More like "allowHtml" + * + * We only allow html_errors if html_errors ini value is true and non-user error + * + * @return bool + */ + private function valIsHtml() + { + return \filter_var(\ini_get('html_errors'), FILTER_VALIDATE_BOOLEAN) + && \in_array($this->values['type'], static::$userErrors, true) === false + && !$this->values['exception']; + } + + /** + * Get initial `isSuppressed` value + * + * @return bool + */ + private function valIsSuppressed() + { + $prevOccurrence = $this->subject->get('error', $this->values['hash']); + if ($prevOccurrence && !$prevOccurrence['isSuppressed']) { + // if any instance of this error was not supprssed, reflect that + return false; + } + $errType = $this->values['type']; + if (($this->subject->getCfg('suppressNever') & $errType) === $errType) { + // never suppress this type + return false; + } + return \error_reporting() === 0; + } +} diff --git a/src/ErrorHandler/AbstractErrorHandler.php b/src/ErrorHandler/AbstractErrorHandler.php index f9cb33cc..a2aaf5fc 100644 --- a/src/ErrorHandler/AbstractErrorHandler.php +++ b/src/ErrorHandler/AbstractErrorHandler.php @@ -4,14 +4,13 @@ * @package bdk\ErrorHandler * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.3 */ namespace bdk\ErrorHandler; use bdk\Backtrace; -use bdk\ErrorHandler; use bdk\ErrorHandler\AbstractComponent; use bdk\ErrorHandler\Error; use bdk\ErrorHandler\Plugin\Emailer; @@ -27,15 +26,20 @@ */ abstract class AbstractErrorHandler extends AbstractComponent { + const EVENT_ERROR = 'errorHandler.error'; + /** @var array */ protected $data = array( 'errorCaller' => array(), 'errors' => array(), - 'lastErrors' => array(), // contains up to two errors: suppressed & unsuppressed + 'lastErrors' => array(), // contains up to two errors: suppressed & unsuppressed // lastError[0] is the most recent error 'uncaughtException' => null, // error constructor will pull this ); + protected $prevErrorHandler = null; + protected $prevExceptionHandler = null; + /** @var Backtrace */ private $backtrace; @@ -53,25 +57,70 @@ abstract class AbstractErrorHandler extends AbstractComponent private $toStringException = null; /** - * Check for anonymous class notation - * Replace with more usefull parent class + * Set data value + * + * @param string $key what + * @param mixed $value value + * + * @return void + */ + public function setData($key, $value) + { + $this->data[$key] = $value; + } + + /** + * Conditioanlly pass error or exception to previously defined handler + * + * @param Error $error Error instance + * + * @return bool + * @throws \Exception + */ + protected function continueToPrevHandler(Error $error) + { + $this->handleUserError($error); + if ($error['continueToPrevHandler'] === false || $error->isPropagationStopped()) { + return $error['continueToNormal'] === false; + } + if ($error['exception']) { + $this->continueToPrevHandlerException($error); + return $error['continueToNormal'] === false; + } + if (!$this->prevErrorHandler) { + return $error['continueToNormal'] === false; + } + return \call_user_func( + $this->prevErrorHandler, + $error['type'], + $error['message'], + $error['file'], + $error['line'], + $error['vars'] + ); + } + + /** + * Restore previous excption handler and re-throw or log exception * * @param Error $error Error instance * * @return void + * @throws \Exception */ - protected function anonymousCheck(Error $error) + private function continueToPrevHandlerException(Error $error) { - $message = $error['message']; - if (\strpos($message, "@anonymous\0") === false) { - return; + if ($this->prevExceptionHandler) { + /* + re-throw exception vs calling handler directly + */ + \restore_exception_handler(); + $this->data['uncaughtException'] = null; + throw $error['exception']; + } + if ($error['continueToNormal']) { + $error->log(); } - $regex = '/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00(.*?\.php)(?:0x?|:([0-9]++)\$)[0-9a-fA-F]++/'; - $error['message'] = \preg_replace_callback($regex, static function ($matches) { - return \class_exists($matches[0], false) - ? (\get_parent_class($matches[0]) ?: \key(\class_implements($matches[0])) ?: 'class') . '@anonymous' - : $matches[0]; - }, $message); } /** @@ -93,7 +142,7 @@ protected function enableStatsEmailer($haveError = false) } $callables = \array_map(static function ($subscriberInfo) { return $subscriberInfo['callable']; - }, $this->eventManager->getSubscribers(ErrorHandler::EVENT_ERROR)); + }, $this->eventManager->getSubscribers(self::EVENT_ERROR)); if ($this->cfg['enableEmailer'] && \in_array(array($this->getEmailer(), 'onErrorHighPri'), $callables, true) === false) { $this->cfg['enableStats'] = true; $this->eventManager->addSubscriberInterface($this->emailer); @@ -196,10 +245,10 @@ protected function onCfgOnError($onError, $prev) Replace - not append - subscriber set via setCfg */ if ($prev !== null) { - $this->eventManager->unsubscribe(ErrorHandler::EVENT_ERROR, $prev); + $this->eventManager->unsubscribe(self::EVENT_ERROR, $prev); } if ($onError) { - $this->eventManager->subscribe(ErrorHandler::EVENT_ERROR, $onError); + $this->eventManager->subscribe(self::EVENT_ERROR, $onError); } } diff --git a/src/ErrorHandler/Error.php b/src/ErrorHandler/Error.php index 1f2e3198..fa6742fc 100644 --- a/src/ErrorHandler/Error.php +++ b/src/ErrorHandler/Error.php @@ -4,16 +4,14 @@ * @package bdk\ErrorHandler * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.3 */ namespace bdk\ErrorHandler; use bdk\ErrorHandler; -use bdk\PubSub\Event; use ErrorException; -use InvalidArgumentException; use ParseError; use ReflectionProperty; @@ -23,79 +21,8 @@ * @property array $context lines surrounding error * @property array $trace backtrace */ -class Error extends Event +class Error extends AbstractError { - const CAT_DEPRECATED = 'deprecated'; - const CAT_ERROR = 'error'; - const CAT_NOTICE = 'notice'; - const CAT_STRICT = 'strict'; - const CAT_WARNING = 'warning'; - const CAT_FATAL = 'fatal'; - - protected static $errCategories = array( - self::CAT_DEPRECATED => array( E_DEPRECATED, E_USER_DEPRECATED ), - self::CAT_ERROR => array( E_USER_ERROR, E_RECOVERABLE_ERROR ), - self::CAT_FATAL => array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_CORE_ERROR ), - self::CAT_NOTICE => array( E_NOTICE, E_USER_NOTICE ), - self::CAT_STRICT => array( E_STRICT ), - self::CAT_WARNING => array( E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING ), - ); - protected static $errTypes = array( - E_ALL => 'E_ALL', // listed here for completeness - E_COMPILE_ERROR => 'Compile Error', // handled via shutdown function - E_COMPILE_WARNING => 'Compile Warning', // handled? - E_CORE_ERROR => 'Core Error', // handled via shutdown function - E_CORE_WARNING => 'Core Warning', // handled? - E_DEPRECATED => 'Deprecated', // php 5.3 : 8192 - E_ERROR => 'Fatal Error', // handled via shutdown function - E_NOTICE => 'Notice', - E_PARSE => 'Parsing Error', // handled via shutdown function - E_RECOVERABLE_ERROR => 'Recoverable Error', // php 5.2 : 4096 - E_STRICT => 'Strict', // php 5.0 : 2048 - E_USER_DEPRECATED => 'User Deprecated', // php 5.3 : 16384 - E_USER_ERROR => 'User Error', - E_USER_NOTICE => 'User Notice', - E_USER_WARNING => 'User Warning', - E_WARNING => 'Warning', - ); - protected static $userErrors = array( - E_USER_DEPRECATED, - E_USER_ERROR, - E_USER_NOTICE, - E_USER_WARNING, - ); - - /** - * Store fatal non-Exception backtrace - * - * Initially null, will become array or false on attempt to get backtrace - * - * @var array|false|null - */ - protected $backtrace = null; - - /** - * @var array Array of key/values - */ - protected $values = array( // phpcs:ignore SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder - 'type' => null, // int: The severity / level / one of the E_* constants - 'message' => '', // The error message - 'evalLine' => null, - 'file' => null, // Filepath the error was raised in - 'line' => null, // Line the error was raised in - 'vars' => array(), // Active symbol table at point error occured - 'category' => null, - 'continueToNormal' => null, // let PHP do its thing (log error / exit if E_USER_ERROR) - 'continueToPrevHandler' => true, - 'exception' => null, - 'hash' => null, - 'isFirstOccur' => true, // per error (ie a error inside a loop, or inside a functon called multiple times) - 'isHtml' => false, - 'isSuppressed' => false, - 'throw' => false, // whether to throw as exception (fatal errors never throw) - 'typeStr' => '', // friendly version of 'type' - ); - /** * Constructor * @@ -107,27 +34,14 @@ class Error extends Event public function __construct(ErrorHandler $errHandler, array $values) { $this->subject = $errHandler; - $this->assertValues($values); + $this->values['constructor'] = true; $this->setValues($values); - unset($this->values['vars']['GLOBALS']); + unset($this->values['constructor']); $errorCaller = $errHandler->get('errorCaller'); - $regexEvaldCode = '/^(.+)\((\d+)\) : eval\(\)\'d code$/'; - $matches = array(); - if (\preg_match($regexEvaldCode, (string) $this->values['file'], $matches)) { - // reported line = line within eval - // line inside paren is the line `eval` is on - $this->values['evalLine'] = $this->values['line']; - $this->values['file'] = $matches[1]; - $this->values['line'] = (int) $matches[2]; - } if ($errorCaller) { $errorCallerVals = \array_intersect_key($errorCaller, \array_flip(array('file', 'line'))); $this->values = \array_merge($this->values, $errorCallerVals); } - if ($this->backtrace === null && \in_array($this->values['type'], array(E_ERROR, E_USER_ERROR), true) && $this->values['exception'] === null) { - // will return empty unless xdebug extension installed/enabled - $this->backtrace = $this->subject->backtrace->get(); - } } /** @@ -155,6 +69,18 @@ public function asException() return $exception; } + /** + * Alias for `getTrace() + * + * @param bool|'auto' $withContext (auto) Whether to include code snippets + * + * @return array|false|null + */ + public function getBacktrace($withContext = 'auto') + { + return $this->getTrace($withContext); + } + /** * Get php code surrounding error * @@ -186,6 +112,18 @@ public function getFileAndLine() return $fileAndLine; } + /** + * Get error message + * + * @return string + */ + public function getMessage() + { + return $this->values['isHtml'] + ? $this->getMessageHtml() + : $this->getMessageText(); + } + /** * Get the message html-escaped * @@ -195,12 +133,12 @@ public function getMessageHtml() { $message = $this->values['message']; return $this->values['isHtml'] - ? $message + ? \str_replace('setValuesInit($values); - $errType = $values['type']; - $hash = $this->hash($values); - $prevOccurrence = $this->subject->get('error', $hash); - $isSuppressed = $this->isSuppressed($prevOccurrence); - $this->values = \array_merge( - $this->values, - array( - 'continueToNormal' => $this->setContinueToNormal($isSuppressed, $prevOccurrence === null), - 'continueToPrevHandler' => $this->subject->getCfg('continueToPrevHandler'), - 'throw' => $this->isFatal() === false && ($errType & $this->subject->getCfg('errorThrow')) === $errType, - ), - $values, - array( - 'category' => $this->values['category'], - 'hash' => $hash, - 'isFirstOccur' => !$prevOccurrence, - 'isHtml' => $this->isHtml(), - 'isSuppressed' => $isSuppressed, - 'message' => $this->isHtml() - ? \str_replace('values['message']) - : $this->values['message'], - 'typeStr' => self::$errTypes[$errType], - ) - ); - } - - /** - * Get human-friendly error type - * - * @param int $type E_xx constant value - * - * @return string - */ - public static function typeStr($type) - { - return isset(self::$errTypes[$type]) - ? self::$errTypes[$type] - : ''; - } - - /** - * Validate error values - * - * @param array $values Initial error values - * - * @return void - * - * @throws InvalidArgumentException - */ - private function assertValues($values) - { - $keysMustHave = array('type', 'message', 'file', 'line'); - $keys = \array_keys($values); - if (\array_intersect($keysMustHave, $keys) !== $keysMustHave) { - throw new InvalidArgumentException('Error values must include: type, message, file, & line'); - } - $validTypes = \array_diff_key(self::$errTypes, \array_flip(array(E_ALL))); - if (\array_key_exists($values['type'], $validTypes) === false) { - throw new InvalidArgumentException('invalid error type specified'); - } - if (\array_key_exists('vars', $values) && \is_array($values['vars']) === false) { - throw new InvalidArgumentException('Error vars must be an array'); - } - } - - /** - * Generate hash used to uniquely identify this error - * - * @return string hash - */ - protected function hash() - { - $errMsg = $this->values['message']; - // (\(.*?)\d+(.*?\)) "(tried to allocate 16384 bytes)" -> "(tried to allocate xxx bytes)" - $errMsg = \preg_replace('/(\(.*?)\d+(.*?\))/', '\1x\2', $errMsg); - // "blah123" -> "blahxxx" - $errMsg = \preg_replace('/\b([a-z]+\d+)+\b/', 'xxx', $errMsg); - // "-123.123" -> "xxx" - $errMsg = \preg_replace('/\b[\d.-]{4,}\b/', 'xxx', $errMsg); - // remove "comments".. this allows throttling email, while still adding unique info to user errors - $errMsg = \preg_replace('/\s*##.+$/', '', $errMsg); - return \md5($this->values['file'] . $this->values['line'] . $this->values['type'] . $errMsg); - } - - /** - * ErrType to category - * - * @param int $errType error type - * - * @return string|null - */ - protected static function getCategory($errType) - { - $return = null; - foreach (self::$errCategories as $category => $errTypes) { - if (\in_array($errType, $errTypes, true)) { - $return = $category; - break; - } + if ($key === 'message') { + $return = $this->getMessage(); + return $return; } + $return =& parent::offsetGet($key); return $return; } - - /** - * isHtml? More like "allowHtml" - * - * We only allow html_errors if html_errors ini value is tru and non-user error - * - * @return bool - */ - private function isHtml() - { - return \filter_var(\ini_get('html_errors'), FILTER_VALIDATE_BOOLEAN) - && \in_array($this->values['type'], static::$userErrors, true) === false - && !$this->values['exception']; - } - - /** - * Get initial `isSuppressed` value - * - * @param self|null $prevOccurrence previous occurrence of current error - * - * @return bool - */ - private function isSuppressed($prevOccurrence = null) - { - if ($prevOccurrence && !$prevOccurrence['isSuppressed']) { - // if any instance of this error was not supprssed, reflect that - return false; - } - $errType = $this->values['type']; - if (($this->subject->getCfg('suppressNever') & $errType) === $errType) { - // never suppress tyis type - return false; - } - return \error_reporting() === 0; - } - - /** - * Set continueToNormal flag - * - * @param bool $isSuppressed Whether error is suppressed - * @param bool $isFirstOccurrence Whether this is errors' first occurrence during this request - * - * @return bool - */ - private function setContinueToNormal($isSuppressed, $isFirstOccurrence) - { - $continueToNormal = $isSuppressed === false && $isFirstOccurrence; - if ($continueToNormal === false || $this->values['category'] !== self::CAT_ERROR) { - return $continueToNormal; - } - // we are a user error - switch ($this->subject->getCfg('onEUserError')) { - case 'continue': - return false; - case 'log': - return false; - case 'normal': - return true; - } - return $continueToNormal; - } - - /** - * Set the core values (type, message, file, line) - * - * @param array $values values being set - * - * @return void - */ - private function setValuesInit($values) - { - $errType = $values['type']; - $category = $this->getCategory($errType); - $this->values = \array_merge( - $this->values, - array( - 'category' => $category, - 'exception' => $this->subject->get('uncaughtException'), // non-null if error is uncaught-exception - ), - $values - ); - if ($this->isFatal()) { - $count = 0; - // fatal message may contain trace info... - // this occurs if fatal encountered in shutdown - $this->values['message'] = \preg_replace( - '/ in \S+\nStack trace:\n(#\d+ .+\n)+ thrown/', - '', - $this->values['message'], - -1, - $count - ); - if ($count) { - // don't try to get trace info. - $this->backtrace = array(); - $this->values['exception'] = null; - } - } - } } diff --git a/src/ErrorHandler/ErrorHandler.php b/src/ErrorHandler/ErrorHandler.php index 0e73d807..37fb1064 100644 --- a/src/ErrorHandler/ErrorHandler.php +++ b/src/ErrorHandler/ErrorHandler.php @@ -4,12 +4,13 @@ * @package bdk\ErrorHandler * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.3 */ namespace bdk; +use bdk\Backtrace; use bdk\ErrorHandler\AbstractErrorHandler; use bdk\ErrorHandler\Error; use bdk\PubSub\Event; @@ -24,15 +25,12 @@ */ class ErrorHandler extends AbstractErrorHandler { - const EVENT_ERROR = 'errorHandler.error'; - /** @var EventManager */ public $eventManager; + protected $inShutdown = false; protected $registered = false; protected $prevDisplayErrors = null; - protected $prevErrorHandler = null; - protected $prevExceptionHandler = null; private static $instance; @@ -188,7 +186,6 @@ public static function getInstance($cfg = array()) public function handleError($errType, $errMsg, $file, $line, $vars = array()) { $error = $this->cfg['errorFactory']($this, $errType, $errMsg, $file, $line, $vars); - $this->anonymousCheck($error); $this->toStringCheck($error); if (!$this->isErrTypeHandled($errType)) { // not handled @@ -203,7 +200,7 @@ public function handleError($errType, $errMsg, $file, $line, $vars = array()) $this->data['errors'][ $error['hash'] ] = $error; if (!$error['isSuppressed']) { // only clear error caller via non-suppressed error - $this->data['errorCaller'] = array(); + $this->setErrorCaller(array()); // only publish event for non-suppressed error $this->eventManager->publish(self::EVENT_ERROR, $error); $this->throwError($error); @@ -304,19 +301,6 @@ public function register() $this->registered = true; // used by this->onShutdown() } - /** - * Set data value - * - * @param string $key what - * @param mixed $value value - * - * @return void - */ - public function setData($key, $value) - { - $this->data[$key] = $value; - } - /** * Set the calling file/line for next error. * This override will apply until cleared or error occurs @@ -335,7 +319,7 @@ public function setData($key, $value) public function setErrorCaller($caller = null, $offset = 0) { if ($caller === null) { - $backtrace = \bdk\Backtrace::get(null, $offset + 3); + $backtrace = Backtrace::get(null, $offset + 3); $index = isset($backtrace[$offset + 1]) ? $offset + 1 : \count($backtrace) - 1; @@ -383,60 +367,6 @@ public function unregister() $this->registered = false; // used by $this->onShutdown() } - /** - * Conditioanlly pass error or exception to previously defined handler - * - * @param Error $error Error instance - * - * @return bool - * @throws \Exception - */ - protected function continueToPrevHandler(Error $error) - { - $this->handleUserError($error); - if ($error['continueToPrevHandler'] === false || $error->isPropagationStopped()) { - return $error['continueToNormal'] === false; - } - if ($error['exception']) { - $this->continueToPrevHandlerException($error); - return $error['continueToNormal'] === false; - } - if (!$this->prevErrorHandler) { - return $error['continueToNormal'] === false; - } - return \call_user_func( - $this->prevErrorHandler, - $error['type'], - $error['message'], - $error['file'], - $error['line'], - $error['vars'] - ); - } - - /** - * Restore previous excption handler and re-throw or log exception - * - * @param Error $error Error instance - * - * @return void - * @throws \Exception - */ - private function continueToPrevHandlerException(Error $error) - { - if ($this->prevExceptionHandler) { - /* - re-throw exception vs calling handler directly - */ - \restore_exception_handler(); - $this->data['uncaughtException'] = null; - throw $error['exception']; - } - if ($error['continueToNormal']) { - $error->log(); - } - } - /** * Create Error instance * diff --git a/src/ErrorHandler/Plugin/Stats.php b/src/ErrorHandler/Plugin/Stats.php index 7fb33e1d..d112b6c7 100644 --- a/src/ErrorHandler/Plugin/Stats.php +++ b/src/ErrorHandler/Plugin/Stats.php @@ -4,7 +4,7 @@ * @package bdk\ErrorHandler * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.2 */ @@ -107,7 +107,7 @@ public function onErrorHighPri(Error $error) $errorStats = $this->dataStore->findByError($error); if ($errorStats) { unset($errorStats['info']); - $errorStats['count'] ++; + $errorStats['count']++; $error['stats'] = \array_merge($error['stats'], $errorStats); } } @@ -131,8 +131,6 @@ public function onErrorLowPri(Error $error) * @param array $prev previous config values * * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function postSetCfg($cfg = array(), $prev = array()) { diff --git a/src/ErrorHandler/Plugin/StatsStoreFile.php b/src/ErrorHandler/Plugin/StatsStoreFile.php index 31117b24..212e5f77 100644 --- a/src/ErrorHandler/Plugin/StatsStoreFile.php +++ b/src/ErrorHandler/Plugin/StatsStoreFile.php @@ -4,7 +4,7 @@ * @package bdk\ErrorHandler * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.2 */ @@ -228,8 +228,6 @@ private function garbageCollectionErr($errorStats, $hash, $tsCutoff) * @param array $prev previous config values * * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function postSetCfg($cfg = array(), $prev = array()) { diff --git a/src/HttpMessage/AbstractServerRequest.php b/src/HttpMessage/AbstractServerRequest.php index d0d628c8..e3ddaf33 100644 --- a/src/HttpMessage/AbstractServerRequest.php +++ b/src/HttpMessage/AbstractServerRequest.php @@ -6,7 +6,7 @@ * @package bdk/http-message * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v1.0 */ @@ -14,7 +14,6 @@ use bdk\HttpMessage\Request; use bdk\HttpMessage\UploadedFile; -use bdk\HttpMessage\Uri; use bdk\HttpMessage\Utility\ContentType; use InvalidArgumentException; use Psr\Http\Message\UploadedFileInterface; diff --git a/src/HttpMessage/UploadedFile.php b/src/HttpMessage/UploadedFile.php index 0edf4024..99b4aff3 100644 --- a/src/HttpMessage/UploadedFile.php +++ b/src/HttpMessage/UploadedFile.php @@ -127,9 +127,6 @@ public function __construct($values = array()) * * @throws RuntimeException in cases when no stream is available or can be * created. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter */ public function getStream() { @@ -458,13 +455,11 @@ private function isOk() * @return bool * * @throws RuntimeException - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter */ private function moveFile($targetPath) { $errMsg = ''; + // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter \set_error_handler(static function ($type, $msg) use (&$errMsg) { $errMsg = $msg; }); @@ -517,7 +512,7 @@ private function setStream($streamOrFile) /** * @return void - * @throws \RuntimeException if is moved or not ok + * @throws RuntimeException if is moved or not ok */ private function validateCanMove() { diff --git a/src/HttpMessage/Utility/Uri.php b/src/HttpMessage/Utility/Uri.php index 7324596f..d8182af9 100644 --- a/src/HttpMessage/Utility/Uri.php +++ b/src/HttpMessage/Utility/Uri.php @@ -176,33 +176,44 @@ private static function parseUrlAddEmpty($parts, $url) private static function pathRemoveDots($path) { if ($path === '') { - return $path; + return ''; } $segments = \explode('/', $path); - $segmentsNew = []; - foreach ($segments as $segment) { - if ($segment === '..') { - \array_pop($segmentsNew); - } elseif ($segment !== '.') { - $segmentsNew[] = $segment; - } - } - + $segmentsNew = self::pathSegments($segments); $pathNew = \implode('/', $segmentsNew); if ($path[0] === '/' && (!isset($pathNew[0]) || $pathNew[0] !== '/')) { // Re-add the leading slash if necessary for cases like "/.." $pathNew = '/' . $pathNew; - } elseif ($pathNew !== '' && \in_array($segment, array('.', '..'), true)) { + } elseif ($pathNew !== '' && \in_array(\end($segments), array('.', '..'), true)) { // Add the trailing slash if necessary - // If pathNew is not empty, then $segment must be set and is the last segment from the foreach $pathNew .= '/'; } return $pathNew; } + /** + * Remove '..' & '.' from path segments + * + * @param array $segments Path segments + * + * @return array + */ + private static function pathSegments($segments) + { + $segmentsNew = []; + foreach ($segments as $segment) { + if ($segment === '..') { + \array_pop($segmentsNew); + } elseif ($segment !== '.') { + $segmentsNew[] = $segment; + } + } + return $segmentsNew; + } + /** * Resolve target path * diff --git a/src/Promise/EachPromise.php b/src/Promise/EachPromise.php index 3f8b6fae..48b2b796 100644 --- a/src/Promise/EachPromise.php +++ b/src/Promise/EachPromise.php @@ -121,23 +121,13 @@ private function addPending() $this->pending[$index] = $promise->then( function ($value) use ($index, $key) { if ($this->onFulfilled) { - \call_user_func( - $this->onFulfilled, - $value, - $key, - $this->aggregate - ); + \call_user_func($this->onFulfilled, $value, $key, $this->aggregate); } $this->step($index); }, function ($reason) use ($index, $key) { if ($this->onRejected) { - \call_user_func( - $this->onRejected, - $reason, - $key, - $this->aggregate - ); + \call_user_func($this->onRejected, $reason, $key, $this->aggregate); } $this->step($index); } diff --git a/src/PubSub/Event.php b/src/PubSub/Event.php index e5f3a4d6..b626d3dd 100644 --- a/src/PubSub/Event.php +++ b/src/PubSub/Event.php @@ -6,7 +6,7 @@ * @package bdk\PubSub * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 * @link http://www.github.com/bkdotcom/PubSub */ @@ -21,14 +21,14 @@ class Event extends ValueStore { /** - * @var bool Whether event subscribers should be called + * @var mixed Event subject - usually object or callable */ - private $propagationStopped = false; + protected $subject = null; /** - * @var mixed Event subject: usually object or callable + * @var bool Whether event subscribers should be called */ - protected $subject = null; + private $propagationStopped = false; /** * Construct an event with optional subject and values diff --git a/src/PubSub/ValueStore.php b/src/PubSub/ValueStore.php index ae3ca360..3660b97c 100644 --- a/src/PubSub/ValueStore.php +++ b/src/PubSub/ValueStore.php @@ -6,7 +6,7 @@ * @package bdk\PubSub * @author Brad Kent * @license http://opensource.org/licenses/MIT MIT - * @copyright 2014-2023 Brad Kent + * @copyright 2014-2024 Brad Kent * @version v3.0 * @link http://www.github.com/bkdotcom/PubSub */ @@ -198,7 +198,9 @@ public function &offsetGet($key) return $this->values[$key]; } $return = null; - $getter = 'get' . \ucfirst($key); + $getter = \preg_match('/^is[A-Z]/', $key) + ? $key + : 'get' . \ucfirst($key); if (\method_exists($this, $getter)) { $return = $this->{$getter}(); } @@ -260,8 +262,6 @@ public function getIterator() * @param array $values key => values being set * * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function onSet($values = array()) { diff --git a/src/Teams/Elements/Table.php b/src/Teams/Elements/Table.php index dcadcfed..16c6ce1c 100644 --- a/src/Teams/Elements/Table.php +++ b/src/Teams/Elements/Table.php @@ -122,27 +122,10 @@ public function withAddedRow($tableRow) */ public function withColumns(array $columns = array()) { - $defaultCol = array( - 'horizontalAlignment' => null, - 'horizontalCellContentAlignment' => null, - 'type' => 'TableColumnDefinition', - 'verticalAlignment' => null, - 'verticalCellContentAlignment' => null, - 'width' => 1, - ); $new = clone $this; - \array_walk($columns, static function ($column, $i) use ($defaultCol, &$new) { - if (\is_array($column) === false) { - throw new InvalidArgumentException(\sprintf('non array TableColumnDefinition value found (index %s)', $i)); - } - $column = \array_merge($defaultCol, $column); - $unknownVals = \array_diff_key($column, $defaultCol); - if (\count($unknownVals) > 0) { - throw new InvalidArgumentException(\sprintf('unknown TableColumnDefinition key "%s" found (index %s)', \key($unknownVals), $i)); - } - if ($column['type'] !== 'TableColumnDefinition') { - throw new InvalidArgumentException(\sprintf('TableColumnDefinition type must be "TableColumnDefinition" (index %s)', $i)); - } + $new->fields['columns'] = array(); + \array_walk($columns, static function ($column, $i) use (&$new) { + $column = self::withColumnsNormalize($column, $i); $new = $new->withAddedColumn( $column['width'], $column['horizontalAlignment'] ?: $column['horizontalCellContentAlignment'], @@ -317,4 +300,38 @@ private function getColCount() } return $colCount; } + + /** + * Merge default values and assert valid definition + * + * @param array $column Column definition + * @param index $index Column index + * + * @return array + * + * @throws InvalidArgumentException + */ + private static function withColumnsNormalize($column, $index) + { + $defaultCol = array( + 'horizontalAlignment' => null, + 'horizontalCellContentAlignment' => null, + 'type' => 'TableColumnDefinition', + 'verticalAlignment' => null, + 'verticalCellContentAlignment' => null, + 'width' => 1, + ); + if (\is_array($column) === false) { + throw new InvalidArgumentException(\sprintf('non array TableColumnDefinition value found (index %s)', $index)); + } + $column = \array_merge($defaultCol, $column); + $unknownVals = \array_diff_key($column, $defaultCol); + if (\count($unknownVals) > 0) { + throw new InvalidArgumentException(\sprintf('unknown TableColumnDefinition key "%s" found (index %s)', \key($unknownVals), $index)); + } + if ($column['type'] !== 'TableColumnDefinition') { + throw new InvalidArgumentException(\sprintf('TableColumnDefinition type must be "TableColumnDefinition" (index %s)', $index)); + } + return $column; + } } diff --git a/src/Teams/Elements/TableCell.php b/src/Teams/Elements/TableCell.php index 9fd58247..90f414a3 100644 --- a/src/Teams/Elements/TableCell.php +++ b/src/Teams/Elements/TableCell.php @@ -31,7 +31,7 @@ class TableCell extends AbstractItem /** * Constructor * - * @param array|ElementInterface|string|numeric $items The card elements to render inside the Container, + * @param array|ElementInterface|\Stringable|scalar|null $items The card elements to render inside the Container, * Or may pass in a single item */ public function __construct($items = array()) @@ -78,7 +78,7 @@ public function getContent($version) /** * Return new instance with added TableCell * - * @param ElementInterface|string|numeric $item Item to append + * @param ElementInterface|\Stringable|scalar|null $item Item to append * * @return static * @@ -109,7 +109,7 @@ public function withBleed($bleed = true) /** * Return new instance with specified items * - * @param array $items The card elements to render inside the TableCell + * @param array $items The card elements to render inside the TableCell * * @return static * @@ -208,7 +208,7 @@ public function withVerticalContentAlignment($alignment) /** * Append ElementInterface, string, or numeric to items * - * @param ElementInterface|string|numeric $item Item to append + * @param ElementInterface|\Stringable|scalar|null $item Item to append * * @return ElementInterface * @@ -216,31 +216,22 @@ public function withVerticalContentAlignment($alignment) */ private function asItem($item) { - if ($item instanceof ElementInterface) { - return $item; - } - if (\is_string($item) || \is_numeric($item)) { - return (new TextBlock($item)) - ->withWrap(); - } - if ($item === null) { - return (new TextBlock('null')) - ->withFontType(Enums::FONT_TYPE_MONOSPACE) - ->withIsSubtle(); - } - if ($item === true) { - return (new TextBlock('true')) - ->withFontType(Enums::FONT_TYPE_MONOSPACE) - ->withColor(Enums::COLOR_GOOD); - } - if ($item === false) { - return (new TextBlock('false')) - ->withFontType(Enums::FONT_TYPE_MONOSPACE) - ->withColor(Enums::COLOR_WARNING); - } - if (\is_object($item) && \method_exists($item, '__toString')) { - return (new TextBlock((string) $item)) - ->withWrap(); + switch (true) { + case $item instanceof ElementInterface: + return $item; + case \is_string($item) || (\is_object($item) && \method_exists($item, '__toString')): + return (new TextBlock((string) $item)) + ->withWrap(); + case \is_numeric($item): + return new TextBlock($item); + case \is_bool($item): + return (new TextBlock(\json_encode($item))) + ->withFontType(Enums::FONT_TYPE_MONOSPACE) + ->withColor($item ? Enums::COLOR_GOOD : Enums::COLOR_WARNING); + case $item === null: + return (new TextBlock('null')) + ->withFontType(Enums::FONT_TYPE_MONOSPACE) + ->withIsSubtle(); } throw new InvalidArgumentException(\sprintf( 'Invalid TableCell item found. Expecting ElementInterface, stringable, scalar, or null. %s provided.', @@ -251,7 +242,7 @@ private function asItem($item) /** * Assert each item is instance of ElementInterface, string, or numeric * - * @param array $items Items to test + * @param array $items Items to test * * @return ElementInterface[] * diff --git a/tests/Debug/Abstraction/AbstractionTest.php b/tests/Debug/Abstraction/AbstractionTest.php index 98e0ba0b..34fbade9 100644 --- a/tests/Debug/Abstraction/AbstractionTest.php +++ b/tests/Debug/Abstraction/AbstractionTest.php @@ -2,9 +2,9 @@ namespace bdk\Test\Debug\Abstraction; -use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; use bdk\Debug\Abstraction\Object\Abstraction as ObjectAbstraction; +use bdk\Debug\Abstraction\Type; use bdk\PubSub\ValueStore; use PHPUnit\Framework\TestCase; @@ -21,7 +21,7 @@ public function testConstruct() $abs = new Abstraction('myType', array('foo' => 'bar')); $this->assertSame('myType', $abs->getValue('type')); $this->assertSame('bar', $abs->getValue('foo')); - $abs = new Abstraction(Abstracter::TYPE_ARRAY); + $abs = new Abstraction(Type::TYPE_ARRAY); $this->assertSame(array(), $abs->getValue('value')); } diff --git a/tests/Debug/Collector/CurlHttpMessageMiddlewareTest.php b/tests/Debug/Collector/CurlHttpMessageMiddlewareTest.php index e85d2a32..c2ec9dd9 100644 --- a/tests/Debug/Collector/CurlHttpMessageMiddlewareTest.php +++ b/tests/Debug/Collector/CurlHttpMessageMiddlewareTest.php @@ -5,6 +5,7 @@ use bdk\CurlHttpMessage\Exception\RequestException; use bdk\CurlHttpMessage\Handler\Mock as MockHandler; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Collector\CurlHttpMessageMiddleware; use bdk\HttpMessage\Request; use bdk\HttpMessage\Response; @@ -102,8 +103,8 @@ public function testSyncronous() 'prettified' => true, 'prettifiedTag' => true, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_JSON, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_JSON, 'value' => '{' . "\n" . ' "foo": "bar"' . "\n" . '}', diff --git a/tests/Debug/Collector/GuzzleMiddlewareTest.php b/tests/Debug/Collector/GuzzleMiddlewareTest.php index 874b12d2..5814b8b0 100644 --- a/tests/Debug/Collector/GuzzleMiddlewareTest.php +++ b/tests/Debug/Collector/GuzzleMiddlewareTest.php @@ -3,6 +3,7 @@ namespace bdk\Test\Debug\Collector; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Collector\GuzzleMiddleware; use bdk\Test\Debug\DebugTestFramework; use Exception; @@ -106,8 +107,8 @@ public function testSyncronous() 'prettified' => true, 'prettifiedTag' => true, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_JSON, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_JSON, 'value' => '{' . "\n" . ' "foo": "bar"' . "\n" . '}', diff --git a/tests/Debug/Collector/OAuthTest.php b/tests/Debug/Collector/OAuthTest.php index 94c0a75b..7beca0cd 100644 --- a/tests/Debug/Collector/OAuthTest.php +++ b/tests/Debug/Collector/OAuthTest.php @@ -3,6 +3,7 @@ namespace bdk\Test\Debug\Collector; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Collector\OAuth; use bdk\Test\Debug\DebugTestFramework; @@ -66,8 +67,8 @@ public function testGetAccessToken() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => '%d' ), 'oauth_version' => '1.0', @@ -206,8 +207,8 @@ public function testGetRequestToken() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => '%d', ), 'oauth_version' => '1.0', @@ -347,8 +348,8 @@ public function testFetch() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => '%d', ), 'oauth_version' => '1.0', @@ -449,8 +450,8 @@ public function testFetchParamsViaSbs() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => '%s', ), 'oauth_version' => '1.0', diff --git a/tests/Debug/Collector/PdoTest.php b/tests/Debug/Collector/PdoTest.php index 408fb060..da4a9de7 100644 --- a/tests/Debug/Collector/PdoTest.php +++ b/tests/Debug/Collector/PdoTest.php @@ -4,6 +4,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Collector\Pdo; use bdk\HttpMessage\Utility\ContentType; use bdk\PhpUnitPolyfill\ExpectExceptionTrait; @@ -150,7 +151,7 @@ public function testExecute() 'prettified' => true, 'prettifiedTag' => false, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, + 'type' => Type::TYPE_STRING, 'typeMore' => null, 'value' => "SELECT \n * \nFROM \n `bob` \nWHERE \n e < :datetime", 'visualWhiteSpace' => false, @@ -172,7 +173,7 @@ public function testExecute() 'type' => array( 'debug' => Abstracter::ABSTRACTION, 'name' => 'PDO::PARAM_STR', - 'type' => Abstracter::TYPE_CONST, + 'type' => Type::TYPE_CONST, 'value' => 2, ), ), diff --git a/tests/Debug/Collector/PhpCurlClassTest.php b/tests/Debug/Collector/PhpCurlClassTest.php index f02d8a89..9f0bdf25 100644 --- a/tests/Debug/Collector/PhpCurlClassTest.php +++ b/tests/Debug/Collector/PhpCurlClassTest.php @@ -80,7 +80,9 @@ public function testPost() )
  • info = array( -
      %A
    )
  • +
      + %A +
    )
  • verbose = %A
  • '; @@ -89,6 +91,8 @@ public function testPost() self::assertSame(2, \preg_match_all('/password=█████████/', $htmlActual), 'Did not find redacted password twice'); $htmlActual = \preg_replace('#^\s+#m', '', $htmlActual); $htmlExpect = \preg_replace('#^\s+#m', '', $htmlExpect); + // echo 'actual: ' . $htmlActual . "\n\n"; + // echo 'expect: ' . $htmlExpect . "\n"; self::assertStringMatchesFormat('%A' . $htmlExpect . '%A', $htmlActual); }, )); diff --git a/tests/Debug/Collector/SoapClientTest.php b/tests/Debug/Collector/SoapClientTest.php index 44743f36..19b094b5 100644 --- a/tests/Debug/Collector/SoapClientTest.php +++ b/tests/Debug/Collector/SoapClientTest.php @@ -3,6 +3,7 @@ namespace bdk\Test\Debug\Collector; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Collector\SoapClient; use bdk\Test\Debug\DebugTestFramework; @@ -49,7 +50,7 @@ public function testConstruct() 'options' => array( 'showListKeys' => false, ), - 'type' => Abstracter::TYPE_ARRAY, + 'type' => Type::TYPE_ARRAY, 'value' => array( 'ProcessSRL(string $SRLFile, string $RequestName, string $key): string', 'ProcessSRL2(string $SRLFile, string $RequestName, string $key1, string $key2): string', @@ -171,7 +172,7 @@ public function testSoapCall() 'class' => array('highlight', 'language-xml'), ), 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_STRING, + 'type' => Type::TYPE_STRING, 'value' => ' @@ -225,7 +226,7 @@ public function testSoapCall() 'class' => array('highlight', 'language-xml'), ), 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_STRING, + 'type' => Type::TYPE_STRING, 'value' => ' @@ -387,7 +388,7 @@ public function testDoRequest() 'class' => array('highlight', 'language-xml'), ), 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_STRING, + 'type' => Type::TYPE_STRING, 'value' => ' @@ -441,7 +442,7 @@ public function testDoRequest() 'class' => array('highlight', 'language-xml'), ), 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_STRING, + 'type' => Type::TYPE_STRING, 'value' => ' diff --git a/tests/Debug/Plugin/InternalEventsTest.php b/tests/Debug/Plugin/InternalEventsTest.php index 098e98c2..5d79c263 100644 --- a/tests/Debug/Plugin/InternalEventsTest.php +++ b/tests/Debug/Plugin/InternalEventsTest.php @@ -13,6 +13,7 @@ /** * PHPUnit tests for Debug class * + * @covers \bdk\Debug\Dump\BaseValue * @covers \bdk\Debug\Plugin\InternalEvents * @covers \bdk\Debug\Plugin\Route * @covers \bdk\Debug\Route\Email diff --git a/tests/Debug/Plugin/LogRequestTest.php b/tests/Debug/Plugin/LogRequestTest.php index bd65aa21..a088225b 100644 --- a/tests/Debug/Plugin/LogRequestTest.php +++ b/tests/Debug/Plugin/LogRequestTest.php @@ -3,7 +3,7 @@ namespace bdk\Test\Debug\Plugin; use bdk\Debug\Abstraction\Abstracter; -use bdk\HttpMessage\Response; +use bdk\Debug\Abstraction\Type; use bdk\HttpMessage\ServerRequest; use bdk\HttpMessage\Stream; use bdk\HttpMessage\UploadedFile; @@ -69,8 +69,8 @@ public function testJsonRequest() 'prettified' => true, 'prettifiedTag' => true, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_JSON, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_JSON, 'value' => \json_encode(\json_decode($requestBody), JSON_PRETTY_PRINT), 'valueDecoded' => \json_decode($requestBody, true), 'visualWhiteSpace' => false, @@ -132,8 +132,8 @@ public function testJsonRequestWrongType() 'prettified' => true, 'prettifiedTag' => true, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_JSON, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_JSON, 'value' => \json_encode(\json_decode($requestBody), JSON_PRETTY_PRINT), 'valueDecoded' => \json_decode($requestBody, true), 'visualWhiteSpace' => false, @@ -313,8 +313,8 @@ public function testPostOnlyFiles() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_NUMERIC, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_NUMERIC, 'value' => '123', ), ), @@ -358,8 +358,8 @@ public function testPostOnlyFiles() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_NUMERIC, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_NUMERIC, 'value' => '123', ), ), @@ -469,8 +469,8 @@ public function testPutMethod() 'prettified' => true, 'prettifiedTag' => true, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_JSON, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_JSON, 'value' => \json_encode(\json_decode($requestBody), JSON_PRETTY_PRINT), 'valueDecoded' => \json_decode($requestBody, true), 'visualWhiteSpace' => false, diff --git a/tests/Debug/Plugin/Method/GeneralTest.php b/tests/Debug/Plugin/Method/GeneralTest.php index a6a5dd33..5e82abfb 100644 --- a/tests/Debug/Plugin/Method/GeneralTest.php +++ b/tests/Debug/Plugin/Method/GeneralTest.php @@ -3,7 +3,7 @@ namespace bdk\Test\Debug\Plugin\Method; use bdk\Debug; -use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Abstraction\Abstraction; use bdk\Test\Debug\DebugTestFramework; @@ -189,7 +189,7 @@ public function testPrettify() $data = array('foo', 'bar'); $json = $this->debug->prettify(\json_encode($data), 'application/json'); self::assertEquals( - new Abstraction(Abstracter::TYPE_STRING, array( + new Abstraction(Type::TYPE_STRING, array( 'strlen' => null, 'typeMore' => 'json', 'value' => \json_encode($data, JSON_PRETTY_PRINT), diff --git a/tests/Debug/Plugin/Method/GroupTest.php b/tests/Debug/Plugin/Method/GroupTest.php index 5421d2ca..13516927 100644 --- a/tests/Debug/Plugin/Method/GroupTest.php +++ b/tests/Debug/Plugin/Method/GroupTest.php @@ -5,6 +5,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\AbstractObject; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; use bdk\Debug\Utility\Reflection; use bdk\PhpUnitPolyfill\ExpectExceptionTrait; @@ -26,6 +27,7 @@ function myFunctionThatCallsGroup() * @covers \bdk\Debug\Abstraction\Abstracter * @covers \bdk\Debug\Dump\Base * @covers \bdk\Debug\Dump\Html + * @covers \bdk\Debug\Dump\Html\Group * @covers \bdk\Debug\Dump\Text * @covers \bdk\Debug\Plugin\Method\Group * @covers \bdk\Debug\Plugin\Method\GroupCleanup @@ -512,7 +514,7 @@ public function testGroupAutoArgs() 'brief' => true, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, + 'type' => Type::TYPE_STRING, 'typeMore' => null, 'value' => '█████████', ) diff --git a/tests/Debug/Plugin/Method/ProfileTest.php b/tests/Debug/Plugin/Method/ProfileTest.php index 23659e10..ec4c55b3 100644 --- a/tests/Debug/Plugin/Method/ProfileTest.php +++ b/tests/Debug/Plugin/Method/ProfileTest.php @@ -4,6 +4,7 @@ use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; use bdk\PubSub\Event; use bdk\Test\Debug\DebugTestFramework; @@ -102,19 +103,19 @@ public function testProfile() 'indexLabel' => null, 'rows' => array( 'bdk\Test\Debug\Plugin\Method\ProfileTest::a' => array( - 'key' => new Abstraction(Abstracter::TYPE_CALLABLE, array( + 'key' => new Abstraction(Type::TYPE_CALLABLE, array( 'hideType' => true, // don't output 'callable' 'value' => 'bdk\Test\Debug\Plugin\Method\ProfileTest::a', )), ), 'bdk\Test\Debug\Plugin\Method\ProfileTest::b' => array( - 'key' => new Abstraction(Abstracter::TYPE_CALLABLE, array( + 'key' => new Abstraction(Type::TYPE_CALLABLE, array( 'hideType' => true, // don't output 'callable' 'value' => 'bdk\Test\Debug\Plugin\Method\ProfileTest::b', )), ), 'bdk\Test\Debug\Plugin\Method\ProfileTest::c' => array( - 'key' => new Abstraction(Abstracter::TYPE_CALLABLE, array( + 'key' => new Abstraction(Type::TYPE_CALLABLE, array( 'hideType' => true, // don't output 'callable' 'value' => 'bdk\Test\Debug\Plugin\Method\ProfileTest::c', )), diff --git a/tests/Debug/Plugin/Method/TableTest.php b/tests/Debug/Plugin/Method/TableTest.php index bc22127c..8ffbc1de 100644 --- a/tests/Debug/Plugin/Method/TableTest.php +++ b/tests/Debug/Plugin/Method/TableTest.php @@ -630,8 +630,8 @@ public static function providerTestMethod() )), Debug::meta('tableInfo', array( 'rows' => array( - 's' => array('key' => 'Sally'), 'b' => array('key' => 'Bob'), + 's' => array('key' => 'Sally'), ), )), ), @@ -643,8 +643,8 @@ public static function providerTestMethod()   ageextracolnameNaughtysex - SallystdClass10yesSallytrueF BobstdClass12BobfalseM + SallystdClass10yesSallytrueF ', diff --git a/tests/Debug/Plugin/PrettifyTest.php b/tests/Debug/Plugin/PrettifyTest.php index 1164eea9..b5012e97 100644 --- a/tests/Debug/Plugin/PrettifyTest.php +++ b/tests/Debug/Plugin/PrettifyTest.php @@ -4,6 +4,7 @@ use bdk\Debug\Abstraction\Abstracter; use bdk\Debug\Abstraction\Abstraction; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Utility\Reflection; use bdk\HttpMessage\Utility\ContentType; use bdk\PhpUnitPolyfill\ExpectExceptionTrait; @@ -38,7 +39,7 @@ public function testPrettify() $html = $this->debug->prettify('test', 'text/html'); self::assertEquals( - new Abstraction(Abstracter::TYPE_STRING, array( + new Abstraction(Type::TYPE_STRING, array( 'strlen' => null, 'typeMore' => null, 'value' => 'test', @@ -61,7 +62,7 @@ public function testPrettify() $data = array('foo', 'bar'); $json = $this->debug->prettify(\json_encode($data), 'application/json'); self::assertEquals( - new Abstraction(Abstracter::TYPE_STRING, array( + new Abstraction(Type::TYPE_STRING, array( 'strlen' => null, 'typeMore' => 'json', 'value' => \json_encode($data, JSON_PRETTY_PRINT), @@ -84,7 +85,7 @@ public function testPrettify() $sql = $this->debug->prettify('SELECT * FROM table WHERE col = "val"', ContentType::SQL); self::assertEquals( - new Abstraction(Abstracter::TYPE_STRING, array( + new Abstraction(Type::TYPE_STRING, array( 'strlen' => null, 'typeMore' => null, 'value' => \str_replace('·', ' ', 'SELECT· @@ -120,7 +121,7 @@ public function testPrettify() '; $xml = $this->debug->prettify(\str_replace("\n", '', $xmlExpect), 'application/xml'); self::assertEquals( - new Abstraction(Abstracter::TYPE_STRING, array( + new Abstraction(Type::TYPE_STRING, array( 'strlen' => null, 'typeMore' => null, 'value' => $xmlExpect, diff --git a/tests/Debug/Route/WampTest.php b/tests/Debug/Route/WampTest.php index 333535a9..3c2a4811 100644 --- a/tests/Debug/Route/WampTest.php +++ b/tests/Debug/Route/WampTest.php @@ -4,6 +4,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Route\Wamp; use bdk\ErrorHandler; use bdk\ErrorHandler\Error; @@ -100,7 +101,7 @@ public function testCrateAbstraction() array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_ARRAY, + 'type' => Type::TYPE_ARRAY, 'value' => array('_b64_:AGZvbw==' => 'bar'), ), array( @@ -108,8 +109,8 @@ public function testCrateAbstraction() 'debug' => Abstracter::ABSTRACTION, 'strlen' => 16, 'strlenValue' => 16, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_BINARY, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_BINARY, 'value' => '_b64_:' . $base64, ), array( @@ -121,8 +122,8 @@ public function testCrateAbstraction() 'prettified' => true, 'prettifiedTag' => true, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_JSON, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_JSON, 'value' => \json_encode(array('foo' => 'bar',42,true,false,null), JSON_PRETTY_PRINT), 'valueDecoded' => array( 'foo' => 'bar',42,true,false,null, diff --git a/tests/Debug/SubstitutionTest.php b/tests/Debug/SubstitutionTest.php index 6ff37ad4..90283ad3 100644 --- a/tests/Debug/SubstitutionTest.php +++ b/tests/Debug/SubstitutionTest.php @@ -4,6 +4,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; /** * PHPUnit tests for Debug Methods @@ -118,8 +119,8 @@ public function testTypesOther() 42, array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_INT, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_INT, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => $time, ), 'boring', @@ -127,8 +128,8 @@ public function testTypesOther() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => 16, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_BINARY, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_BINARY, 'value' => $binary, ), ), diff --git a/tests/Debug/Type/ArrayTest.php b/tests/Debug/Type/ArrayTest.php index 93f1bf64..f33cc193 100644 --- a/tests/Debug/Type/ArrayTest.php +++ b/tests/Debug/Type/ArrayTest.php @@ -4,6 +4,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Test\Debug\DebugTestFramework; /** @@ -237,7 +238,7 @@ public function testMaxDepth() 'options' => array( 'isMaxDepth' => true, ), - 'type' => Abstracter::TYPE_ARRAY, + 'type' => Type::TYPE_ARRAY, 'value' => array(), ), 'ding' => 'dong', diff --git a/tests/Debug/Type/BasicTest.php b/tests/Debug/Type/BasicTest.php index 067bc0fb..1b513eb3 100644 --- a/tests/Debug/Type/BasicTest.php +++ b/tests/Debug/Type/BasicTest.php @@ -4,6 +4,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Test\Debug\DebugTestFramework; /** @@ -11,6 +12,7 @@ * * @covers \bdk\Debug\Abstraction\Abstracter * @covers \bdk\Debug\Abstraction\AbstractString + * @covers \bdk\Debug\Abstraction\Type * @covers \bdk\Debug\Dump\BaseValue * @covers \bdk\Debug\Dump\Html * @covers \bdk\Debug\Dump\Html\HtmlString @@ -34,7 +36,7 @@ public static function providerTestMethod() 'args' => array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_RESOURCE, + 'type' => Type::TYPE_RESOURCE, 'value' => \print_r($fhOpen, true) . ': stream', ), ), @@ -47,7 +49,7 @@ public static function providerTestMethod() 'args' => array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_RESOURCE, + 'type' => Type::TYPE_RESOURCE, 'value' => \print_r($fhClosed, true) . ': stream', ), ), @@ -101,7 +103,7 @@ public static function providerTestMethod() array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_CALLABLE, + 'type' => Type::TYPE_CALLABLE, 'value' => array( 'bdk\Test\Debug\Fixture\TestObj', 'testBaseStatic', @@ -117,7 +119,7 @@ public static function providerTestMethod() array( Debug::_getInstance()->abstracter->crateWithVals( 'SomeNamespace\Classname', - array('typeMore' => Abstracter::TYPE_STRING_CLASSNAME) + array('typeMore' => Type::TYPE_STRING_CLASSNAME) ), ), array( @@ -128,8 +130,8 @@ public static function providerTestMethod() 'debug' => Abstracter::ABSTRACTION, 'brief' => false, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_CLASSNAME, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_CLASSNAME, 'value' => 'SomeNamespace\Classname', ) ), @@ -169,9 +171,9 @@ public static function providerTestMethod() array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_FLOAT, - 'typeMore' => Abstracter::TYPE_FLOAT_INF, - 'value' => Abstracter::TYPE_FLOAT_INF, + 'type' => Type::TYPE_FLOAT, + 'typeMore' => Type::TYPE_FLOAT_INF, + 'value' => Type::TYPE_FLOAT_INF, ), ), ), @@ -191,9 +193,9 @@ public static function providerTestMethod() array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_FLOAT, - 'typeMore' => Abstracter::TYPE_FLOAT_NAN, - 'value' => Abstracter::TYPE_FLOAT_NAN, + 'type' => Type::TYPE_FLOAT, + 'typeMore' => Type::TYPE_FLOAT_NAN, + 'value' => Type::TYPE_FLOAT_NAN, ), ), ), @@ -286,8 +288,8 @@ public static function providerTestMethod() 'args' => array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_FLOAT, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_FLOAT, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => $ts + 0.1, ), ), @@ -303,8 +305,8 @@ public static function providerTestMethod() array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_FLOAT, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_FLOAT, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => $ts + 0.1, ), ), @@ -320,8 +322,8 @@ public static function providerTestMethod() 'args' => array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_INT, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_INT, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => $ts, ), ), @@ -337,8 +339,8 @@ public static function providerTestMethod() array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_INT, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_INT, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => $ts, ), ), @@ -349,7 +351,7 @@ public static function providerTestMethod() 'log', array( Debug::getInstance()->abstracter->crateWithVals(\strtotime('1985-10-26 09:00:00 PDT'), array( - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'typeMore' => Type::TYPE_TIMESTAMP, )), ), array( @@ -363,8 +365,8 @@ public static function providerTestMethod() array( array( 'debug' => Abstracter::ABSTRACTION, - 'type' => Abstracter::TYPE_INT, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_INT, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => 499190400, ), ), @@ -387,8 +389,8 @@ public static function providerTestMethod() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => (string) $ts, ) ), @@ -399,7 +401,7 @@ public static function providerTestMethod() 'log', array( Debug::getInstance()->abstracter->crateWithVals((string) \strtotime('1985-10-26 09:00:00 PDT'), array( - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'typeMore' => Type::TYPE_TIMESTAMP, )), ), array( @@ -415,8 +417,8 @@ public static function providerTestMethod() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => '499190400', ), ), @@ -450,8 +452,8 @@ public static function providerTestMethod() 'brief' => false, 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_TIMESTAMP, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_TIMESTAMP, 'value' => (string) $ts, ), ), @@ -508,7 +510,7 @@ public static function providerTestMethod() 'log', array( Debug::getInstance()->abstracter->crateWithVals('mysteryVal', array( - 'type' => Abstracter::TYPE_UNKNOWN, + 'type' => Type::TYPE_UNKNOWN, )), ), array( @@ -519,7 +521,7 @@ public static function providerTestMethod() 'brief' => false, // crateWithVals... initially treated as string 'debug' => Abstracter::ABSTRACTION, 'strlen' => null, - 'type' => Abstracter::TYPE_UNKNOWN, + 'type' => Type::TYPE_UNKNOWN, 'typeMore' => null, 'value' => 'mysteryVal', ), diff --git a/tests/Debug/Type/EnumTest.php b/tests/Debug/Type/EnumTest.php index 8255f719..c7a8bd08 100644 --- a/tests/Debug/Type/EnumTest.php +++ b/tests/Debug/Type/EnumTest.php @@ -4,6 +4,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; use bdk\Test\Debug\DebugTestFramework; @@ -39,7 +40,7 @@ public static function providerTestMethod() 'entry' => static function (LogEntry $logEntry) { $abs = $logEntry['args'][0]; self::assertAbstractionType($abs); - self::assertSame(Abstracter::TYPE_OBJECT, $abs['type']); + self::assertSame(Type::TYPE_OBJECT, $abs['type']); $cases = $abs['cases']; \ksort($cases); diff --git a/tests/Debug/Type/ObjectTest.php b/tests/Debug/Type/ObjectTest.php index 5a897d23..d367879a 100644 --- a/tests/Debug/Type/ObjectTest.php +++ b/tests/Debug/Type/ObjectTest.php @@ -17,17 +17,18 @@ * @covers \bdk\Debug\Abstraction\Abstracter * @covers \bdk\Debug\Abstraction\Abstraction * @covers \bdk\Debug\Abstraction\AbstractObject + * @covers \bdk\Debug\Abstraction\Object\AbstractInheritable * @covers \bdk\Debug\Abstraction\Object\Abstraction * @covers \bdk\Debug\Abstraction\Object\Constants * @covers \bdk\Debug\Abstraction\Object\Definition * @covers \bdk\Debug\Abstraction\Object\Helper - * @covers \bdk\Debug\Abstraction\Object\Inheritable * @covers \bdk\Debug\Abstraction\Object\MethodParams * @covers \bdk\Debug\Abstraction\Object\Methods * @covers \bdk\Debug\Abstraction\Object\Properties * @covers \bdk\Debug\Abstraction\Object\PropertiesDom * @covers \bdk\Debug\Abstraction\Object\PropertiesPhpDoc * @covers \bdk\Debug\Abstraction\Object\Subscriber + * @covers \bdk\Debug\Abstraction\Type * @covers \bdk\Debug\Dump\Base * @covers \bdk\Debug\Dump\BaseValue * @covers \bdk\Debug\Dump\Html @@ -37,6 +38,7 @@ * @covers \bdk\Debug\Dump\Html\ObjectCases * @covers \bdk\Debug\Dump\Html\ObjectConstants * @covers \bdk\Debug\Dump\Html\ObjectMethods + * @covers \bdk\Debug\Dump\Html\ObjectPhpDoc * @covers \bdk\Debug\Dump\Html\ObjectProperties * @covers \bdk\Debug\Dump\Html\Value * @covers \bdk\Debug\Dump\Text @@ -941,8 +943,10 @@ public function testAbstraction() 'defaultValue' => Abstracter::UNDEFINED, 'desc' => 'first param' . "\n" . 'two-line description!', 'isOptional' => false, + 'isPassedByReference' => false, 'isPromoted' => false, - 'name' => '$param1', + 'isVariadic' => false, + 'name' => 'param1', 'type' => 'stdClass', ), array( @@ -950,8 +954,10 @@ public function testAbstraction() 'defaultValue' => array(), 'desc' => 'second param', 'isOptional' => true, + 'isPassedByReference' => false, 'isPromoted' => false, - 'name' => '$param2', + 'isVariadic' => false, + 'name' => 'param2', 'type' => 'array', ), ), @@ -1145,9 +1151,9 @@ public function testAnonymousClass() ), \array_keys($abs->getInheritedValues()['properties'])); self::assertSame(array( + 'magic', 'test', 'test1', - 'magic', ), \array_keys($abs->getInheritedValues()['methods'])); self::assertArraySubset( @@ -1542,7 +1548,17 @@ public function testVariadic() } $testVar = new \bdk\Test\Debug\Fixture\TestVariadic(); $abs = $this->debug->abstracter->getAbstraction($testVar); - self::assertSame('...$moreParams', $abs['methods']['methodVariadic']['params'][1]['name']); + self::assertSame(array( + 'attributes' => array(), + 'defaultValue' => Abstracter::UNDEFINED, + 'desc' => 'variadic param (PHP 5.6)', + 'isOptional' => true, + 'isPassedByReference' => false, + 'isPromoted' => false, + 'isVariadic' => true, + 'name' => 'moreParams', + 'type' => 'mixed', + ), $abs['methods']['methodVariadic']['params'][1]); } public function testVariadicByReference() @@ -1555,7 +1571,17 @@ public function testVariadicByReference() } $testVarByRef = new \bdk\Test\Debug\Fixture\TestVariadicByReference(); $abs = $this->debug->abstracter->getAbstraction($testVarByRef); - self::assertSame('&...$moreParams', $abs['methods']['methodVariadicByReference']['params'][1]['name']); + self::assertSame(array( + 'attributes' => array(), + 'defaultValue' => Abstracter::UNDEFINED, + 'desc' => 'variadic param by reference (PHP 5.6)', + 'isOptional' => true, + 'isPassedByReference' => true, + 'isPromoted' => false, + 'isVariadic' => true, + 'name' => 'moreParams', + 'type' => 'mixed', + ), $abs['methods']['methodVariadicByReference']['params'][1]); } public function testCollectPropertyValues() diff --git a/tests/Debug/Type/StringTest.php b/tests/Debug/Type/StringTest.php index 910a60ad..f37d8b19 100644 --- a/tests/Debug/Type/StringTest.php +++ b/tests/Debug/Type/StringTest.php @@ -4,6 +4,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\LogEntry; use bdk\Test\Debug\DebugTestFramework; @@ -416,8 +417,8 @@ public static function providerTestMethod() 'debug' => Abstracter::ABSTRACTION, 'brief' => false, 'strlen' => 16, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_BINARY, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_BINARY, 'value' => \base64_decode('j/v9wNrF5i1abMXFW/4vVw==', true), ), ), @@ -444,8 +445,8 @@ public static function providerTestMethod() 'debug' => Abstracter::ABSTRACTION, 'brief' => true, 'strlen' => 16, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_BINARY, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_BINARY, 'value' => \base64_decode('j/v9wNrF5i1abMXFW/4vVw==', true), ), ), @@ -470,8 +471,8 @@ public static function providerTestMethod() 'debug' => Abstracter::ABSTRACTION, 'brief' => true, 'strlen' => \filesize(TEST_DIR . '/assets/logo.png'), - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_BINARY, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_BINARY, 'contentType' => 'image/png', 'value' => '', ), @@ -531,8 +532,8 @@ public static function providerTestMethod() 'debug' => Abstracter::ABSTRACTION, 'brief' => true, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_JSON, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_JSON, 'value' => '{"poop":"\ud83d\udca9","int":42,"password":"█████████"}', 'valueDecoded' => null, ), @@ -570,8 +571,8 @@ public static function providerTestMethod() 'debug' => Abstracter::ABSTRACTION, 'brief' => false, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_SERIALIZED, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_SERIALIZED, 'value' => 'a:1:{s:3:"foo";s:3:"bar";}', 'valueDecoded' => array( 'foo' => 'bar', @@ -603,8 +604,8 @@ public static function providerTestMethod() 'debug' => Abstracter::ABSTRACTION, 'brief' => true, 'strlen' => null, - 'type' => Abstracter::TYPE_STRING, - 'typeMore' => Abstracter::TYPE_STRING_SERIALIZED, + 'type' => Type::TYPE_STRING, + 'typeMore' => Type::TYPE_STRING_SERIALIZED, 'value' => 'a:1:{s:3:"foo";s:3:"bar";}', 'valueDecoded' => null, ), diff --git a/tests/Debug/Utility/FileStreamWrapperTest.php b/tests/Debug/Utility/FileStreamWrapperTest.php index 41097561..d776230c 100644 --- a/tests/Debug/Utility/FileStreamWrapperTest.php +++ b/tests/Debug/Utility/FileStreamWrapperTest.php @@ -12,6 +12,7 @@ * PHPUnit tests for Debug class * * @covers \bdk\Debug\Utility\FileStreamWrapper + * @covers \bdk\Debug\Utility\FileStreamWrapperBase */ class FileStreamWrapperTest extends TestCase { diff --git a/tests/Debug/Utility/PhpDocTest.php b/tests/Debug/Utility/PhpDocTest.php index 6124d93c..88c28656 100644 --- a/tests/Debug/Utility/PhpDocTest.php +++ b/tests/Debug/Utility/PhpDocTest.php @@ -12,7 +12,11 @@ * PHPUnit tests for Debug class * * @covers \bdk\Debug\Utility\PhpDoc - * @covers \bdk\Debug\Utility\PhpDocBase + * @covers \bdk\Debug\Utility\PhpDoc\Helper + * @covers \bdk\Debug\Utility\PhpDoc\ParseMethod + * @covers \bdk\Debug\Utility\PhpDoc\ParseParam + * @covers \bdk\Debug\Utility\PhpDoc\Parsers + * @covers \bdk\Debug\Utility\PhpDoc\Type */ class PhpDocTest extends TestCase { @@ -26,7 +30,8 @@ public static function setUpBeforeClass(): void public function testConstruct() { $phpDoc = new PhpDoc(); - $parsers = \bdk\Debug\Utility\Reflection::propGet($phpDoc, 'parsers'); + $parsersObj = \bdk\Debug\Utility\Reflection::propGet($phpDoc, 'parsers'); + $parsers = \bdk\Debug\Utility\Reflection::propGet($parsersObj, 'parsers'); self::assertIsArray($parsers); self::assertNotEmpty($parsers, 'Parsers is empty'); } @@ -56,22 +61,26 @@ public function testGetParsedObject() "name": "presto", "param": [ { - "name": "$foo", + "isVariadic": false, + "name": "foo", "type": null }, { "defaultValue": "1", - "name": "$int", + "isVariadic": false, + "name": "int", "type": "int" }, { "defaultValue": "true", - "name": "$bool", + "isVariadic": false, + "name": "bool", "type": null }, { "defaultValue": "null", - "name": "$null", + "isVariadic": false, + "name": "null", "type": null } ], @@ -83,17 +92,20 @@ public function testGetParsedObject() "name": "prestoStatic", "param": [ { - "name": "$noDefault", + "isVariadic": false, + "name": "noDefault", "type": "string" }, { "defaultValue": "array()", - "name": "$arr", + "isVariadic": false, + "name": "arr", "type": null }, { "defaultValue": "array('a'=>'ay','b'=>'bee')", - "name": "$opts", + "isVariadic": false, + "name": "opts", "type": null } ], @@ -226,7 +238,8 @@ public static function providerMethod() 'param' => array( array( 'desc' => 'plain ol object', - 'name' => '$obj', + 'isVariadic' => false, + 'name' => 'obj', 'type' => 'stdClass', ), ), @@ -343,6 +356,7 @@ public static function providerStrings() 'param' => array( array( 'desc' => null, + 'isVariadic' => false, 'name' => 'comment', 'type' => 'string', ), @@ -363,6 +377,7 @@ public static function providerStrings() 'param' => array( array( 'desc' => 'comment here', + 'isVariadic' => false, 'name' => null, 'type' => 'string', ), @@ -389,7 +404,8 @@ public static function providerStrings() 'param' => array( array( 'desc' => "Some number\ndoes things", - 'name' => '$number', + 'isVariadic' => false, + 'name' => 'number', 'type' => 'int[]', ), ), diff --git a/tests/Debug/Utility/SerializeLogTest.php b/tests/Debug/Utility/SerializeLogTest.php index dd345c02..3bcf0aac 100644 --- a/tests/Debug/Utility/SerializeLogTest.php +++ b/tests/Debug/Utility/SerializeLogTest.php @@ -4,6 +4,7 @@ use bdk\Debug; use bdk\Debug\Abstraction\Abstracter; +use bdk\Debug\Abstraction\Type; use bdk\Debug\Utility\SerializeLog; use bdk\HttpMessage\ServerRequest; use bdk\PhpUnitPolyfill\AssertionTrait; @@ -209,7 +210,7 @@ public function testUnserializeLogLegacy() ), 'scopeClass' => 'bdk\Debug', 'stringified' => null, - 'type' => Abstracter::TYPE_OBJECT, + 'type' => Type::TYPE_OBJECT, 'traverseValues' => array(), 'viaDebugInfo' => false, ), diff --git a/tests/Teams/Elements/TableCellTest.php b/tests/Teams/Elements/TableCellTest.php index 2599ef4d..38905c8c 100644 --- a/tests/Teams/Elements/TableCellTest.php +++ b/tests/Teams/Elements/TableCellTest.php @@ -124,12 +124,12 @@ public function testGetContent() array( 'type' => 'TextBlock', 'text' => '3.14', - 'wrap' => true, + // 'wrap' => true, ), array( 'type' => 'TextBlock', 'text' => '42', - 'wrap' => true, + // 'wrap' => true, ), ), 'minHeight' => '42px', diff --git a/tests/Teams/Elements/TableRowTest.php b/tests/Teams/Elements/TableRowTest.php index dfc92bf7..87240e36 100644 --- a/tests/Teams/Elements/TableRowTest.php +++ b/tests/Teams/Elements/TableRowTest.php @@ -148,7 +148,7 @@ protected static function withTestCases() array( 'type' => 'TextBlock', 'text' => '3.14', - 'wrap' => true, + // 'wrap' => true, ), ), ), diff --git a/tests/Teams/Elements/TableTest.php b/tests/Teams/Elements/TableTest.php index 59a96850..5a834b9f 100644 --- a/tests/Teams/Elements/TableTest.php +++ b/tests/Teams/Elements/TableTest.php @@ -50,7 +50,7 @@ public function testConstruct() array( 'type' => 'TextBlock', 'text' => '26', - 'wrap' => true, + // 'wrap' => true, ), ), ), @@ -75,7 +75,7 @@ public function testConstruct() array( 'type' => 'TextBlock', 'text' => '25', - 'wrap' => true, + // 'wrap' => true, ), ), ), diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 154c63dd..e0f226c6 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -31,7 +31,7 @@ */ $httpdCfg = array( - 'errorLogPath' => __DIR__ . '/docroot/httpd_error_log.txt', + 'errorLogPath' => __DIR__ . '/../tmp/httpd_error_log.txt', ); /* @@ -78,8 +78,15 @@ }, )); +$debug->eventManager->subscribe(\bdk\ErrorHandler::EVENT_ERROR, static function (\bdk\ErrorHandler\Error $error) { + if ($error['continueToNormal'] && $error['throw'] === false) { + throw new \PHPUnit\Framework\Error($error['message']); + } +}, -1); + $debug->eventManager->subscribe(\bdk\PubSub\Manager::EVENT_PHP_SHUTDOWN, static function () use ($httpdCfg) { httpdStop(); + outputHttpErrorLog($httpdCfg); $files = \array_merge( \glob(TEST_DIR . '/../tmp/log/*.json'), \glob(TEST_DIR . '/../tmp/*') @@ -89,7 +96,6 @@ \unlink($file); } } - outputHttpErrorLog($httpdCfg); }, 0 - PHP_INT_MAX); httpdStart($httpdCfg); @@ -108,7 +114,7 @@ function httpdStart($cfg = array()) { $cfg = \array_merge(array( - 'errorLogPath' => __DIR__ . '/docroot/httpd_error_log.txt', + 'errorLogPath' => __DIR__ . '/../tmp/httpd_error_log.txt', ), $cfg); // php 7.0 seems to e borked. // unable to specify -t docroot and -f frontController.php @@ -132,7 +138,6 @@ function httpdStart($cfg = array()) \usleep(250000); // wait .25 sec for server to get going echo \stream_get_contents($pipes[2]) . "\n"; \chdir($dirWas); - \file_put_contents($cfg['errorLogPath'], ''); } /** @@ -168,7 +173,9 @@ function httpdStop() */ function outputHttpErrorLog($httpdCfg = array()) { - $errorLogContents = \file_get_contents($httpdCfg['errorLogPath']); + $errorLogContents = \file_exists($httpdCfg['errorLogPath']) + ? \file_get_contents($httpdCfg['errorLogPath']) + : ''; return $errorLogContents ? "\n" . "\e[38;5;88;48;5;203;1;4m" . 'http error log:' . "\e[0m" . "\n" . $errorLogContents . "\n\n" : ''; diff --git a/tests/docroot/frontController.php b/tests/docroot/frontController.php index c4195412..0a7b7930 100644 --- a/tests/docroot/frontController.php +++ b/tests/docroot/frontController.php @@ -19,7 +19,7 @@ 'route' => 'wamp', )); $debug->eventManager->subscribe(\bdk\ErrorHandler::EVENT_ERROR, static function (\bdk\ErrorHandler\Error $error) use ($requestUri) { - $logFile = __DIR__ . '/httpd_error_log.txt'; + $logFile = __DIR__ . '/../../tmp/httpd_error_log.txt'; $logEntry = \sprintf( '[%s] %s', $requestUri,