8889841cPK9[ README.mdnuW+A# Send PHP errors to Flare [![Latest Version on Packagist](https://img.shields.io/packagist/v/facade/flare-client-php.svg?style=flat-square)](https://packagist.org/packages/facade/flare-client-php) ![Tests](https://github.com/facade/flare-client-php/workflows/Run%20tests/badge.svg) [![Total Downloads](https://img.shields.io/packagist/dt/facade/flare-client-php.svg?style=flat-square)](https://packagist.org/packages/facade/flare-client-php) This repository contains a PHP client to send PHP errors to [Flare](https://flareapp.io). ![Screenshot of error in Flare](https://facade.github.io/flare-client-php/screenshot.png) ## Documentation You can find the documentation of this package at [the docs of Flare](https://flareapp.io/docs/general/projects). ## Changelog Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. ## Testing ``` bash composer test ``` ## Contributing Please see [CONTRIBUTING](CONTRIBUTING.md) for details. ## Security If you discover any security related issues, please email support@flareapp.io instead of using the issue tracker. ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. PK9[p'BB LICENSE.mdnuW+AThe MIT License (MIT) Copyright (c) Facade Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK9[Pfile = $file; $this->lineNumber = $lineNumber; $this->method = $method; $this->class = $class; } public function toArray(): array { $codeSnippet = (new Codesnippet()) ->snippetLineCount(9) ->surroundingLine($this->lineNumber) ->get($this->file); return [ 'line_number' => $this->lineNumber, 'method' => $this->getFullMethod(), 'code_snippet' => $codeSnippet, 'file' => $this->file, ]; } private function getFullMethod(): string { $method = $this->method; if ($class = $this->class ?? false) { $method = "{$class}::{$method}"; } return $method; } public function getFile(): string { return $this->file; } } PK9[I{-"-" src/Flare.phpnuW+AdetermineVersionCallable = $determineVersionCallable; } public function reportErrorLevels(int $reportErrorLevels) { $this->reportErrorLevels = $reportErrorLevels; } public function filterExceptionsUsing(callable $filterExceptionsCallable) { $this->filterExceptionsCallable = $filterExceptionsCallable; } public function filterReportsUsing(callable $filterReportsCallable) { $this->filterReportsCallable = $filterReportsCallable; } /** * @return null|string */ public function version() { if (! $this->determineVersionCallable) { return null; } return ($this->determineVersionCallable)(); } public function __construct(Client $client, ContextDetectorInterface $contextDetector = null, Container $container = null, array $middleware = []) { $this->client = $client; $this->recorder = new Recorder(); $this->contextDetector = $contextDetector ?? new ContextContextDetector(); $this->container = $container; $this->middleware = $middleware; $this->api = new Api($this->client); $this->registerDefaultMiddleware(); } public function getMiddleware(): array { return $this->middleware; } public function registerFlareHandlers() { $this->registerExceptionHandler(); $this->registerErrorHandler(); return $this; } public function registerExceptionHandler() { $this->previousExceptionHandler = set_exception_handler([$this, 'handleException']); return $this; } public function registerErrorHandler() { $this->previousErrorHandler = set_error_handler([$this, 'handleError']); return $this; } private function registerDefaultMiddleware() { return $this->registerMiddleware(new AddGlows($this->recorder)); } public function registerMiddleware($callable) { $this->middleware[] = $callable; return $this; } public function getMiddlewares(): array { return $this->middleware; } public function glow( string $name, string $messageLevel = MessageLevels::INFO, array $metaData = [] ) { $this->recorder->record(new Glow($name, $messageLevel, $metaData)); } public function handleException(Throwable $throwable) { $this->report($throwable); if ($this->previousExceptionHandler) { call_user_func($this->previousExceptionHandler, $throwable); } } public function handleError($code, $message, $file = '', $line = 0) { $exception = new ErrorException($message, 0, $code, $file, $line); $this->report($exception); if ($this->previousErrorHandler) { return call_user_func( $this->previousErrorHandler, $message, $code, $file, $line ); } } public function applicationPath(string $applicationPath) { $this->applicationPath = $applicationPath; return $this; } public function report(Throwable $throwable, callable $callback = null): ?Report { if (! $this->shouldSendReport($throwable)) { return null; } $report = $this->createReport($throwable); if (! is_null($callback)) { call_user_func($callback, $report); } $this->sendReportToApi($report); return $report; } protected function shouldSendReport(Throwable $throwable): bool { if ($this->reportErrorLevels && $throwable instanceof Error) { return $this->reportErrorLevels & $throwable->getCode(); } if ($this->reportErrorLevels && $throwable instanceof ErrorException) { return $this->reportErrorLevels & $throwable->getSeverity(); } if ($this->filterExceptionsCallable && $throwable instanceof Exception) { return call_user_func($this->filterExceptionsCallable, $throwable); } return true; } public function reportMessage(string $message, string $logLevel, callable $callback = null) { $report = $this->createReportFromMessage($message, $logLevel); if (! is_null($callback)) { call_user_func($callback, $report); } $this->sendReportToApi($report); } public function sendTestReport(Throwable $throwable) { $this->api->sendTestReport($this->createReport($throwable)); } private function sendReportToApi(Report $report) { if ($this->filterReportsCallable) { if (! call_user_func($this->filterReportsCallable, $report)) { return; } } try { $this->api->report($report); } catch (Exception $exception) { } } public function reset() { $this->api->sendQueuedReports(); $this->userProvidedContext = []; $this->recorder->reset(); } private function applyAdditionalParameters(Report $report) { $report ->stage($this->stage) ->messageLevel($this->messageLevel) ->setApplicationPath($this->applicationPath) ->userProvidedContext($this->userProvidedContext); } public function anonymizeIp() { $this->registerMiddleware(new AnonymizeIp()); return $this; } public function censorRequestBodyFields(array $fieldNames) { $this->registerMiddleware(new CensorRequestBodyFields($fieldNames)); return $this; } public function createReport(Throwable $throwable): Report { $report = Report::createForThrowable( $throwable, $this->contextDetector->detectCurrentContext(), $this->applicationPath, $this->version() ); return $this->applyMiddlewareToReport($report); } public function createReportFromMessage(string $message, string $logLevel): Report { $report = Report::createForMessage( $message, $logLevel, $this->contextDetector->detectCurrentContext(), $this->applicationPath ); return $this->applyMiddlewareToReport($report); } protected function applyMiddlewareToReport(Report $report): Report { $this->applyAdditionalParameters($report); $report = (new Pipeline($this->container)) ->send($report) ->through($this->middleware) ->then(function ($report) { return $report; }); return $report; } } PK9[6,src/Glows/Recorder.phpnuW+Aglows[] = $glow; $this->glows = array_slice($this->glows, static::GLOW_LIMIT * -1, static::GLOW_LIMIT); } public function glows(): array { return $this->glows; } public function reset() { $this->glows = []; } } PK9[|u,src/Glows/Glow.phpnuW+Aname = $name; $this->messageLevel = $messageLevel; $this->metaData = $metaData; $this->microtime = $microtime ?? microtime(true); } public function toArray() { return [ 'time' => $this->getCurrentTime(), 'name' => $this->name, 'message_level' => $this->messageLevel, 'meta_data' => $this->metaData, 'microtime' => $this->microtime, ]; } } PK9[ yy&src/Contracts/ProvidesFlareContext.phpnuW+Aheaders = $headers; $this->body = $body; $this->error = $error; } /** * @return mixed */ public function getHeaders() { return $this->headers; } /** * @return mixed */ public function getBody() { return $this->body; } /** * @return bool */ public function hasBody() { return $this->body != false; } /** * @return mixed */ public function getError() { return $this->error; } /** * @return null|int */ public function getHttpResponseCode() { if (! isset($this->headers['http_code'])) { return; } return (int) $this->headers['http_code']; } } PK9[L`]#src/Http/Exceptions/BadResponse.phpnuW+AgetError()}"); $exception->response = $response; return $exception; } } PK9[!D$ src/Http/Exceptions/NotFound.phpnuW+Aresponse = $response; $bodyErrors = isset($response->getBody()['errors']) ? $response->getBody()['errors'] : []; $exception->errors = $bodyErrors; return $exception; } public static function getMessageForResponse(Response $response) { return "Response code {$response->getHttpResponseCode()} returned"; } } PK9[#C(src/Http/Exceptions/MissingParameter.phpnuW+AapiToken = $apiToken; $this->apiSecret = $apiSecret; if (! $baseUrl) { throw MissingParameter::create('baseUrl'); } $this->baseUrl = $baseUrl; if (! $timeout) { throw MissingParameter::create('timeout'); } $this->timeout = $timeout; } /** * @param string $url * @param array $arguments * * @return array|false */ public function get(string $url, array $arguments = []) { return $this->makeRequest('get', $url, $arguments); } /** * @param string $url * @param array $arguments * * @return array|false */ public function post(string $url, array $arguments = []) { return $this->makeRequest('post', $url, $arguments); } /** * @param string $url * @param array $arguments * * @return array|false */ public function patch(string $url, array $arguments = []) { return $this->makeRequest('patch', $url, $arguments); } /** * @param string $url * @param array $arguments * * @return array|false */ public function put(string $url, array $arguments = []) { return $this->makeRequest('put', $url, $arguments); } /** * @param string $method * @param array $arguments * * @return array|false */ public function delete(string $method, array $arguments = []) { return $this->makeRequest('delete', $method, $arguments); } /** * @param string $httpVerb * @param string $url * @param array $arguments * * @return array */ private function makeRequest(string $httpVerb, string $url, array $arguments = []) { $queryString = http_build_query([ 'key' => $this->apiToken, 'secret' => $this->apiSecret, ]); $fullUrl = "{$this->baseUrl}/{$url}?{$queryString}"; $headers = [ 'x-api-token: '.$this->apiToken, ]; $response = $this->makeCurlRequest($httpVerb, $fullUrl, $headers, $arguments); if ($response->getHttpResponseCode() === 422) { throw InvalidData::createForResponse($response); } if ($response->getHttpResponseCode() === 404) { throw NotFound::createForResponse($response); } if ($response->getHttpResponseCode() !== 200 && $response->getHttpResponseCode() !== 204) { throw BadResponseCode::createForResponse($response); } return $response->getBody(); } public function makeCurlRequest(string $httpVerb, string $fullUrl, array $headers = [], array $arguments = []): Response { $curlHandle = $this->getCurlHandle($fullUrl, $headers); switch ($httpVerb) { case 'post': curl_setopt($curlHandle, CURLOPT_POST, true); $this->attachRequestPayload($curlHandle, $arguments); break; case 'get': curl_setopt($curlHandle, CURLOPT_URL, $fullUrl.'&'.http_build_query($arguments)); break; case 'delete': curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'DELETE'); break; case 'patch': curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'PATCH'); $this->attachRequestPayload($curlHandle, $arguments); break; case 'put': curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'PUT'); $this->attachRequestPayload($curlHandle, $arguments); break; } $body = json_decode(curl_exec($curlHandle), true); $headers = curl_getinfo($curlHandle); $error = curl_error($curlHandle); return new Response($headers, $body, $error); } private function attachRequestPayload(&$curlHandle, array $data) { $encoded = json_encode($data); $this->lastRequest['body'] = $encoded; curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $encoded); } /** * @param string $fullUrl * @param array $headers * * @return resource */ private function getCurlHandle(string $fullUrl, array $headers = []) { $curlHandle = curl_init(); curl_setopt($curlHandle, CURLOPT_URL, $fullUrl); curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array_merge([ 'Accept: application/json', 'Content-Type: application/json', ], $headers)); curl_setopt($curlHandle, CURLOPT_USERAGENT, 'Laravel/Flare API 1.0'); curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true); curl_setopt($curlHandle, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); curl_setopt($curlHandle, CURLOPT_ENCODING, ''); curl_setopt($curlHandle, CURLINFO_HEADER_OUT, true); curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true); curl_setopt($curlHandle, CURLOPT_MAXREDIRS, 1); return $curlHandle; } } PK9[7 src/Api.phpnuW+Aclient = $client; register_shutdown_function([$this, 'sendQueuedReports']); } public static function sendReportsInBatches(bool $batchSending = true) { static::$sendInBatches = $batchSending; } public function report(Report $report) { try { if (static::$sendInBatches) { $this->addReportToQueue($report); } else { $this->sendReportToApi($report); } } catch (Exception $e) { // } } public function sendTestReport(Report $report) { $this->sendReportToApi($report); } protected function addReportToQueue(Report $report) { $this->queue[] = $report; } public function sendQueuedReports() { try { foreach ($this->queue as $report) { $this->sendReportToApi($report); } } catch (Exception $e) { // } finally { $this->queue = []; } } protected function sendReportToApi(Report $report) { $this->client->post('reports', $this->truncateReport($report->toArray())); } protected function truncateReport(array $payload): array { return (new ReportTrimmer())->trim($payload); } } PK9[ Ssrc/Concerns/UsesTime.phpnuW+AgetCurrentTime(); } } PK9[Usrc/Concerns/HasContext.phpnuW+Astage = $stage; return $this; } public function messageLevel(?string $messageLevel) { $this->messageLevel = $messageLevel; return $this; } public function getGroup(string $groupName = 'context', $default = []): array { return $this->userProvidedContext[$groupName] ?? $default; } public function context($key, $value) { return $this->group('context', [$key => $value]); } public function group(string $groupName, array $properties) { $group = $this->userProvidedContext[$groupName] ?? []; $this->userProvidedContext[$groupName] = array_merge_recursive_distinct( $group, $properties ); return $this; } } PK9[<src/Stacktrace/Frame.phpnuW+Afile = $file; $this->lineNumber = $lineNumber; $this->method = $method; $this->class = $class; $this->isApplicationFrame = $isApplicationFrame; } public function toArray(): array { $codeSnippet = (new Codesnippet()) ->snippetLineCount(31) ->surroundingLine($this->lineNumber) ->get($this->file); return [ 'line_number' => $this->lineNumber, 'method' => $this->method, 'class' => $this->class, 'code_snippet' => $codeSnippet, 'file' => $this->file, 'is_application_frame' => $this->isApplicationFrame, ]; } public function getFile(): string { return $this->file; } public function getLinenumber(): int { return $this->lineNumber; } public function isApplicationFrame() { return $this->isApplicationFrame; } } PK9[Zhsrc/Stacktrace/Codesnippet.phpnuW+AsurroundingLine = $surroundingLine; return $this; } public function snippetLineCount(int $snippetLineCount): self { $this->snippetLineCount = $snippetLineCount; return $this; } public function get(string $fileName): array { if (! file_exists($fileName)) { return []; } try { $file = new File($fileName); [$startLineNumber, $endLineNumber] = $this->getBounds($file->numberOfLines()); $code = []; $line = $file->getLine($startLineNumber); $currentLineNumber = $startLineNumber; while ($currentLineNumber <= $endLineNumber) { $code[$currentLineNumber] = rtrim(substr($line, 0, 250)); $line = $file->getNextLine(); $currentLineNumber++; } return $code; } catch (RuntimeException $exception) { return []; } } private function getBounds($totalNumberOfLineInFile): array { $startLine = max($this->surroundingLine - floor($this->snippetLineCount / 2), 1); $endLine = $startLine + ($this->snippetLineCount - 1); if ($endLine > $totalNumberOfLineInFile) { $endLine = $totalNumberOfLineInFile; $startLine = max($endLine - ($this->snippetLineCount - 1), 1); } return [$startLine, $endLine]; } } PK9[grϑ src/Stacktrace/Stacktrace.phpnuW+AgetTrace(), $applicationPath, $throwable->getFile(), $throwable->getLine()); } public static function create(?string $applicationPath = null): self { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS & ~DEBUG_BACKTRACE_PROVIDE_OBJECT); return new static($backtrace, $applicationPath); } public function __construct(array $backtrace, ?string $applicationPath = null, string $topmostFile = null, string $topmostLine = null) { $this->applicationPath = $applicationPath; $currentFile = $topmostFile; $currentLine = $topmostLine; foreach ($backtrace as $rawFrame) { if (! $this->frameFromFlare($rawFrame) && ! $this->fileIgnored($currentFile)) { $this->frames[] = new Frame( $currentFile, $currentLine, $rawFrame['function'] ?? null, $rawFrame['class'] ?? null, $this->frameFileFromApplication($currentFile) ); } $currentFile = $rawFrame['file'] ?? 'unknown'; $currentLine = $rawFrame['line'] ?? 0; } $this->frames[] = new Frame( $currentFile, $currentLine, '[top]' ); } protected function frameFromFlare(array $rawFrame): bool { return isset($rawFrame['class']) && strpos($rawFrame['class'], 'Facade\\FlareClient\\') === 0; } protected function frameFileFromApplication(string $frameFilename): bool { $relativeFile = str_replace('\\', DIRECTORY_SEPARATOR, $frameFilename); if (! empty($this->applicationPath)) { $relativeFile = array_reverse(explode($this->applicationPath ?? '', $frameFilename, 2))[0]; } if (strpos($relativeFile, DIRECTORY_SEPARATOR . 'vendor') === 0) { return false; } return true; } protected function fileIgnored(string $currentFile): bool { $currentFile = str_replace('\\', DIRECTORY_SEPARATOR, $currentFile); $ignoredFiles = [ '/ignition/src/helpers.php', ]; foreach ($ignoredFiles as $ignoredFile) { if (strstr($currentFile, $ignoredFile) !== false) { return true; } } return false; } public function firstFrame(): Frame { return $this->frames[0]; } public function toArray(): array { return array_map(function (Frame $frame) { return $frame->toArray(); }, $this->frames); } public function firstApplicationFrame(): ?Frame { foreach ($this->frames as $index => $frame) { if ($frame->isApplicationFrame()) { return $frame; } } return null; } public function firstApplicationFrameIndex(): ?int { foreach ($this->frames as $index => $frame) { if ($frame->isApplicationFrame()) { return $index; } } return null; } } PK9[^src/Stacktrace/File.phpnuW+Afile = new SplFileObject($path); } public function numberOfLines(): int { $this->file->seek(PHP_INT_MAX); return $this->file->key() + 1; } public function getLine(int $lineNumber = null): string { if (is_null($lineNumber)) { return $this->getNextLine(); } $this->file->seek($lineNumber - 1); return $this->file->current(); } public function getNextLine(): string { $this->file->next(); return $this->file->current(); } } PK9[Fosrc/Time/SystemTime.phpnuW+AgetTimestamp(); } } PK9[јiisrc/Time/Time.phpnuW+Afile = $file; $this->data = $data; } public static function create(string $file, array $data = []): self { return new static($file, $data); } private function dumpViewData($variable): string { $cloner = new VarCloner(); $dumper = new HtmlDumper(); $dumper->setDumpHeader(''); $output = fopen('php://memory', 'r+b'); $dumper->dump($cloner->cloneVar($variable)->withMaxDepth(1), $output, [ 'maxDepth' => 1, 'maxStringLength' => 160, ]); return stream_get_contents($output, -1, 0); } public function toArray() { return [ 'file' => $this->file, 'data' => array_map([$this, 'dumpViewData'], $this->data), ]; } } PK9["Isrc/Enums/MessageLevels.phpnuW+ArunningInConsole()) { return new ConsoleContext($_SERVER['argv'] ?? []); } return new RequestContext(); } private function runningInConsole(): bool { if (isset($_ENV['APP_RUNNING_IN_CONSOLE'])) { return $_ENV['APP_RUNNING_IN_CONSOLE'] === 'true'; } if (isset($_ENV['FLARE_FAKE_WEB_REQUEST'])) { return false; } return in_array(php_sapi_name(), ['cli', 'phpdb']); } } PK9[AW||src/Context/ConsoleContext.phpnuW+Aarguments = $arguments; } public function toArray(): array { return [ 'arguments' => $this->arguments, ]; } } PK9[ss src/Context/ContextInterface.phpnuW+Arequest = $request ?? Request::createFromGlobals(); } public function getRequest(): array { return [ 'url' => $this->request->getUri(), 'ip' => $this->request->getClientIp(), 'method' => $this->request->getMethod(), 'useragent' => $this->request->headers->get('User-Agent'), ]; } private function getFiles(): array { if (is_null($this->request->files)) { return []; } return $this->mapFiles($this->request->files->all()); } protected function mapFiles(array $files) { return array_map(function ($file) { if (is_array($file)) { return $this->mapFiles($file); } if (! $file instanceof UploadedFile) { return; } try { $fileSize = $file->getSize(); } catch (\RuntimeException $e) { $fileSize = 0; } try { $mimeType = $file->getMimeType(); } catch (InvalidArgumentException $e) { $mimeType = 'undefined'; } return [ 'pathname' => $file->getPathname(), 'size' => $fileSize, 'mimeType' => $mimeType, ]; }, $files); } public function getSession(): array { try { $session = $this->request->getSession(); } catch (\Exception $exception) { $session = []; } return $session ? $this->getValidSessionData($session) : []; } /** * @param SessionInterface $session * @return array */ protected function getValidSessionData($session): array { try { json_encode($session->all()); } catch (Throwable $e) { return []; } return $session->all(); } public function getCookies(): array { return $this->request->cookies->all(); } public function getHeaders(): array { return $this->request->headers->all(); } public function getRequestData(): array { return [ 'queryString' => $this->request->query->all(), 'body' => $this->request->request->all(), 'files' => $this->getFiles(), ]; } public function toArray(): array { return [ 'request' => $this->getRequest(), 'request_data' => $this->getRequestData(), 'headers' => $this->getHeaders(), 'cookies' => $this->getCookies(), 'session' => $this->getSession(), ]; } } PK9[bP9˓(src/Context/ContextDetectorInterface.phpnuW+Asolution = $solution; } public static function fromSolution(SolutionContract $solution) { return new static($solution); } public function toArray(): array { $isRunnable = ($this->solution instanceof RunnableSolution); return [ 'class' => get_class($this->solution), 'title' => $this->solution->getSolutionTitle(), 'description' => $this->solution->getSolutionDescription(), 'links' => $this->solution->getDocumentationLinks(), 'action_description' => $isRunnable ? $this->solution->getSolutionActionDescription() : null, 'is_runnable' => $isRunnable, ]; } } PK9[G,*src/Middleware/CensorRequestBodyFields.phpnuW+AfieldNames = $fieldNames; } public function handle(Report $report, $next) { $context = $report->allContext(); foreach ($this->fieldNames as $fieldName) { if (isset($context['request_data']['body'][$fieldName])) { $context['request_data']['body'][$fieldName] = ''; } } $report->userProvidedContext($context); return $next($report); } } PK9[^src/Middleware/AddGlows.phpnuW+Arecorder = $recorder; } public function handle(Report $report, $next) { foreach ($this->recorder->glows() as $glow) { $report->addGlow($glow); } return $next($report); } } PK9[KKsrc/Middleware/AnonymizeIp.phpnuW+AallContext(); $context['request']['ip'] = null; $report->userProvidedContext($context); return $next($report); } } PK9[X4src/helpers.phpnuW+A &$value) { if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { $merged[$key] = array_merge_recursive_distinct($merged[$key], $value); } else { $merged[$key] = $value; } } return $merged; } } PK9[|H H src/Report.phpnuW+AsetApplicationPath($applicationPath) ->throwable($throwable) ->useContext($context) ->exceptionClass(self::getClassForThrowable($throwable)) ->message($throwable->getMessage()) ->stackTrace(Stacktrace::createForThrowable($throwable, $applicationPath)) ->exceptionContext($throwable) ->setApplicationVersion($version); } protected static function getClassForThrowable(Throwable $throwable): string { if ($throwable instanceof \Facade\Ignition\Exceptions\ViewException) { if ($previous = $throwable->getPrevious()) { return get_class($previous); } } return get_class($throwable); } public static function createForMessage(string $message, string $logLevel, ContextInterface $context, ?string $applicationPath = null): self { $stacktrace = Stacktrace::create($applicationPath); return (new static()) ->setApplicationPath($applicationPath) ->message($message) ->useContext($context) ->exceptionClass($logLevel) ->stacktrace($stacktrace) ->openFrameIndex($stacktrace->firstApplicationFrameIndex()); } public function __construct() { $this->trackingUuid = self::$fakeTrackingUuid ?? $this->generateUuid(); } public function trackingUuid(): string { return $this->trackingUuid; } public function exceptionClass(string $exceptionClass) { $this->exceptionClass = $exceptionClass; return $this; } public function getExceptionClass(): string { return $this->exceptionClass; } public function throwable(Throwable $throwable) { $this->throwable = $throwable; return $this; } public function getThrowable(): ?Throwable { return $this->throwable; } public function message(string $message) { $this->message = $message; return $this; } public function getMessage(): string { return $this->message; } public function stacktrace(Stacktrace $stacktrace) { $this->stacktrace = $stacktrace; return $this; } public function getStacktrace(): Stacktrace { return $this->stacktrace; } public function notifierName(string $notifierName) { $this->notifierName = $notifierName; return $this; } public function languageVersion(string $languageVersion) { $this->languageVersion = $languageVersion; return $this; } public function frameworkVersion(string $frameworkVersion) { $this->frameworkVersion = $frameworkVersion; return $this; } public function useContext(ContextInterface $request) { $this->context = $request; return $this; } public function openFrameIndex(?int $index) { $this->openFrameIndex = $index; return $this; } public function setApplicationPath(?string $applicationPath) { $this->applicationPath = $applicationPath; return $this; } public function getApplicationPath(): ?string { return $this->applicationPath; } public function setApplicationVersion(?string $applicationVersion) { $this->applicationVersion = $applicationVersion; return $this; } public function getApplicationVersion(): ?string { return $this->applicationVersion; } public function view(?View $view) { $this->view = $view; return $this; } public function addGlow(Glow $glow) { $this->glows[] = $glow->toArray(); return $this; } public function addSolution(Solution $solution) { $this->solutions[] = ReportSolution::fromSolution($solution)->toArray(); return $this; } public function userProvidedContext(array $userProvidedContext) { $this->userProvidedContext = $userProvidedContext; return $this; } /** @deprecated */ public function groupByTopFrame() { $this->groupBy = GroupingTypes::TOP_FRAME; return $this; } /** @deprecated */ public function groupByException() { $this->groupBy = GroupingTypes::EXCEPTION; return $this; } public function allContext(): array { $context = $this->context->toArray(); $context = array_merge_recursive_distinct($context, $this->exceptionContext); return array_merge_recursive_distinct($context, $this->userProvidedContext); } private function exceptionContext(Throwable $throwable) { if ($throwable instanceof ProvidesFlareContext) { $this->exceptionContext = $throwable->context(); } return $this; } public function toArray() { return [ 'notifier' => $this->notifierName ?? 'Flare Client', 'language' => 'PHP', 'framework_version' => $this->frameworkVersion, 'language_version' => $this->languageVersion ?? phpversion(), 'exception_class' => $this->exceptionClass, 'seen_at' => $this->getCurrentTime(), 'message' => $this->message, 'glows' => $this->glows, 'solutions' => $this->solutions, 'stacktrace' => $this->stacktrace->toArray(), 'context' => $this->allContext(), 'stage' => $this->stage, 'message_level' => $this->messageLevel, 'open_frame_index' => $this->openFrameIndex, 'application_path' => $this->applicationPath, 'application_version' => $this->applicationVersion, 'tracking_uuid' => $this->trackingUuid, ]; } /* * Found on https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555 */ private function generateUuid(): string { // Generate 16 bytes (128 bits) of random data or use the data passed into the function. $data = $data ?? random_bytes(16); // Set version to 0100 $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // Set bits 6-7 to 10 $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // Output the 36 character UUID. return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); } } PK9[t%src/Truncation/TruncationStrategy.phpnuW+AreportTrimmer = $reportTrimmer; } } PK9[]Doo&src/Truncation/TrimStringsStrategy.phpnuW+AreportTrimmer->needsToBeTrimmed($payload)) { break; } $payload = $this->trimPayloadString($payload, $threshold); } return $payload; } protected function trimPayloadString(array $payload, int $threshold): array { array_walk_recursive($payload, function (&$value) use ($threshold) { if (is_string($value) && strlen($value) > $threshold) { $value = substr($value, 0, $threshold); } }); return $payload; } } PK9[7%Mii+src/Truncation/TrimContextItemsStrategy.phpnuW+AreportTrimmer->needsToBeTrimmed($payload)) { break; } $payload['context'] = $this->iterateContextItems($payload['context'], $threshold); } return $payload; } protected function iterateContextItems(array $contextItems, int $threshold): array { array_walk($contextItems, [$this, 'trimContextItems'], $threshold); return $contextItems; } protected function trimContextItems(&$value, $key, int $threshold) { if (is_array($value)) { if (count($value) > $threshold) { $value = array_slice($value, $threshold * -1, $threshold); } array_walk($value, [$this, 'trimContextItems'], $threshold); } return $value; } } PK9[%T src/Truncation/ReportTrimmer.phpnuW+Astrategies as $strategy) { if (! $this->needsToBeTrimmed($payload)) { break; } $payload = (new $strategy($this))->execute($payload); } return $payload; } public function needsToBeTrimmed(array $payload): bool { return strlen(json_encode($payload)) > self::getMaxPayloadSize(); } public static function getMaxPayloadSize(): int { return self::$maxPayloadSize; } public static function setMaxPayloadSize(int $maxPayloadSize): void { self::$maxPayloadSize = $maxPayloadSize; } } PK9[ @@ composer.jsonnuW+A{ "name": "facade/flare-client-php", "description": "Send PHP errors to Flare", "keywords": [ "facade", "flare", "exception", "reporting" ], "homepage": "https://github.com/facade/flare-client-php", "license": "MIT", "require": { "php": "^7.1|^8.0", "facade/ignition-contracts": "~1.0", "illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0", "symfony/http-foundation": "^3.3|^4.1|^5.0", "symfony/mime": "^3.4|^4.0|^5.1", "symfony/var-dumper": "^3.4|^4.0|^5.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.14", "spatie/phpunit-snapshot-assertions": "^2.0", "phpunit/phpunit": "^7.5" }, "autoload": { "psr-4": { "Facade\\FlareClient\\": "src" }, "files": [ "src/helpers.php" ] }, "autoload-dev": { "psr-4": { "Facade\\FlareClient\\Tests\\": "tests" } }, "scripts": { "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes", "test": "vendor/bin/phpunit", "test-coverage": "vendor/bin/phpunit --coverage-html coverage" }, "config": { "sort-packages": true }, "extra": { "branch-alias": { "dev-master": "1.0-dev" } } } PK9[l.php-cs-fixer.phpnuW+AnotPath('bootstrap/*') ->notPath('storage/*') ->notPath('resources/view/mail/*') ->in([ __DIR__ . '/src', __DIR__ . '/tests', ]) ->name('*.php') ->notName('*.blade.php') ->notName('GitConflictController.php') ->ignoreDotFiles(true) ->ignoreVCS(true); return (new PhpCsFixer\Config()) ->setRules([ '@PSR12' => true, 'array_syntax' => ['syntax' => 'short'], 'ordered_imports' => ['sort_algorithm' => 'alpha'], 'no_unused_imports' => true, 'not_operator_with_successor_space' => true, 'trailing_comma_in_multiline' => true, 'phpdoc_scalar' => true, 'unary_operator_spaces' => true, 'binary_operator_spaces' => true, 'blank_line_before_statement' => [ 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], ], 'phpdoc_single_line_var_spacing' => true, 'phpdoc_var_without_name' => true, 'class_attributes_separation' => [ 'elements' => [ 'method' => 'one', ], ], 'method_argument_space' => [ 'on_multiline' => 'ensure_fully_multiline', 'keep_multiple_spaces_after_comma' => true, ], 'single_trait_insert_per_statement' => true, ]) ->setFinder($finder); PK9[B.php-cs-fixer.cachenuW+A{"php":"8.1.8","version":"3.9.5","indent":" ","lineEnding":"\n","rules":{"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"braces":{"allow_single_line_anonymous_class_with_empty_body":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_typehint":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"sort_algorithm":"alpha"},"return_type_declaration":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_import_per_statement":{"group_to_single_imports":false},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline","keep_multiple_spaces_after_comma":true},"no_break_comment":true,"no_closing_tag":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"no_unused_imports":true,"not_operator_with_successor_space":true,"trailing_comma_in_multiline":true,"phpdoc_scalar":true,"unary_operator_spaces":true,"binary_operator_spaces":true,"blank_line_before_statement":{"statements":["break","continue","declare","return","throw","try"]},"phpdoc_single_line_var_spacing":true,"phpdoc_var_without_name":true,"class_attributes_separation":{"elements":{"method":"one"}}},"hashes":{"src\/Enums\/GroupingTypes.php":794823242,"src\/Enums\/MessageLevels.php":2252993314,"src\/Flare.php":2076395954,"src\/Glows\/Glow.php":2938926460,"src\/Glows\/Recorder.php":741799100,"src\/Contracts\/ProvidesFlareContext.php":3654647562,"src\/Concerns\/UsesTime.php":2203258051,"src\/Concerns\/HasContext.php":2283577173,"src\/Middleware\/AnonymizeIp.php":2582237091,"src\/Middleware\/AddGlows.php":2757630618,"src\/Middleware\/CensorRequestBodyFields.php":3157018559,"src\/Report.php":4287930236,"src\/Truncation\/ReportTrimmer.php":1411772441,"src\/Truncation\/TruncationStrategy.php":2876512000,"src\/Truncation\/TrimStringsStrategy.php":1149918480,"src\/Truncation\/TrimContextItemsStrategy.php":2488083767,"src\/Truncation\/AbstractTruncationStrategy.php":3738899546,"src\/Stacktrace\/File.php":2191220409,"src\/Stacktrace\/Frame.php":2201806026,"src\/Stacktrace\/Codesnippet.php":1758681831,"src\/Stacktrace\/Stacktrace.php":3487330919,"src\/Http\/Response.php":3175166434,"src\/Http\/Exceptions\/BadResponse.php":2136825932,"src\/Http\/Exceptions\/InvalidData.php":4224780353,"src\/Http\/Exceptions\/MissingParameter.php":3963873571,"src\/Http\/Exceptions\/NotFound.php":605045793,"src\/Http\/Exceptions\/BadResponseCode.php":3629899270,"src\/Http\/Client.php":3513047095,"src\/View.php":1758466251,"src\/helpers.php":878229930,"src\/Frame.php":1560844999,"src\/Solutions\/ReportSolution.php":1414311092,"src\/Time\/Time.php":3063453905,"src\/Time\/SystemTime.php":1875330795,"src\/Context\/ContextInterface.php":3556428806,"src\/Context\/ContextDetectorInterface.php":3409530978,"src\/Context\/RequestContext.php":916577092,"src\/Context\/ContextContextDetector.php":1433590813,"src\/Context\/ConsoleContext.php":2891436865,"src\/Api.php":4147640268,"tests\/Glows\/RecorderTest.php":946753721,"tests\/Concerns\/MatchesDumpSnapshots.php":3419801558,"tests\/Concerns\/MatchesCodeSnippetSnapshots.php":685279681,"tests\/Concerns\/MatchesReportSnapshots.php":2362276842,"tests\/Mocks\/FakeClient.php":1694586070,"tests\/Truncation\/TrimContextItemsStrategyTest.php":112441598,"tests\/Truncation\/TrimStringsStrategyTest.php":2187306892,"tests\/TestClasses\/ExceptionWithContext.php":3729019575,"tests\/TestClasses\/DumpDriver.php":3041182929,"tests\/TestClasses\/Assert.php":151773303,"tests\/TestClasses\/CodeSnippetDriver.php":615657858,"tests\/TestClasses\/ReportDriver.php":3461657491,"tests\/TestClasses\/FakeTime.php":2977551027,"tests\/Stacktrace\/StrackTraceTest.php":757065022,"tests\/Stacktrace\/CodesnippetTest.php":765068665,"tests\/Stacktrace\/FileTest.php":420603250,"tests\/Stacktrace\/CodeSnippetDriver.php":4244799073,"tests\/Stacktrace\/ThrowAndReturnExceptionAction.php":4198281204,"tests\/Stacktrace\/__snapshots__\/StrackTraceTest__it_can_detect_application_frames__1.php":167790125,"tests\/FlareTest.php":2320308878,"tests\/ReportTest.php":1195087882,"tests\/TestCase.php":996592721,"tests\/Context\/ConsoleContextTest.php":3637467343,"tests\/Context\/RequestContextTest.php":1346110003}}PK9[ README.mdnuW+APK9[p'BB LICENSE.mdnuW+APK9[P