clock-work

This commit is contained in:
noor
2023-04-24 17:39:09 +05:30
committed by RafficMohammed
parent cf4bec91a6
commit 1eea7ff15e
178 changed files with 13169 additions and 123 deletions

View File

@@ -0,0 +1,36 @@
<?php namespace Clockwork\Helpers\Concerns;
use Clockwork\Helpers\StackFrame;
// Replaces the first stack frame rendering a Laravel view with a duplicate with a resolved original view path (instead
// of the compiled view path)
trait ResolvesViewName
{
public function resolveViewName()
{
$viewFrame = $this->first(function ($frame) {
return $frame->shortPath ? preg_match('#^/storage/framework/views/[a-z0-9]+\.php$#', $frame->shortPath) : false;
});
if (! $viewFrame) return $this;
$renderFrame = $this->first(function ($frame) {
return $frame->call == 'Illuminate\View\View->getContents()'
&& $frame->object instanceof \Illuminate\View\View;
});
if (! $renderFrame) return $this;
$resolvedViewFrame = new StackFrame(
[ 'file' => $renderFrame->object->getPath(), 'line' => $viewFrame->line ],
$this->basePath,
$this->vendorPath
);
return $this->copy(array_merge(
array_slice($this->frames, 0, array_search($viewFrame, $this->frames)),
[ $resolvedViewFrame ],
array_slice($this->frames, array_search($viewFrame, $this->frames) + 2)
));
}
}

View File

@@ -0,0 +1,139 @@
<?php namespace Clockwork\Helpers;
// Prepares various types of data for serialization
class Serializer
{
// Serialized objects cache by object hash
protected $cache = [];
// Options for the current instance
protected $options = [];
// Default options for new instances
protected static $defaults = [
'blackbox' => [
\Illuminate\Container\Container::class,
\Illuminate\Foundation\Application::class,
\Laravel\Lumen\Application::class
],
'limit' => 10,
'toArray' => false,
'toString' => false,
'debugInfo' => true,
'jsonSerialize' => false,
'traces' => true,
'tracesFilter' => null,
'tracesSkip' => null,
'tracesLimit' => null
];
// Create a new instance optionally with options overriding defaults
public function __construct(array $options = [])
{
$this->options = $options + static::$defaults;
}
// Set default options for all new instances
public static function defaults(array $defaults)
{
static::$defaults = $defaults + static::$defaults;
}
// Prepares the passed data to be ready for serialization, takes any kind of data to normalize as the first
// argument, other arguments are used internally in recursion
public function normalize($data, $context = null, $limit = null)
{
if ($context === null) $context = [ 'references' => [] ];
if ($limit === null) $limit = $this->options['limit'];
if (is_array($data)) {
if ($limit === 0) return [ '__type__' => 'array', '__omitted__' => 'limit' ];
return [ '__type__' => 'array' ] + $this->normalizeEach($data, $context, $limit - 1);
} elseif (is_object($data)) {
if ($data instanceof \Closure) return [ '__type__' => 'anonymous function' ];
$className = get_class($data);
$objectHash = spl_object_hash($data);
if ($limit === 0) return [ '__class__' => $className, '__omitted__' => 'limit' ];
if (isset($context['references'][$objectHash])) return [ '__type__' => 'recursion' ];
$context['references'][$objectHash] = true;
if (isset($this->cache[$objectHash])) return $this->cache[$objectHash];
if ($this->options['blackbox'] && in_array($className, $this->options['blackbox'])) {
return $this->cache[$objectHash] = [ '__class__' => $className, '__omitted__' => 'blackbox' ];
} elseif ($this->options['toString'] && method_exists($data, '__toString')) {
return $this->cache[$objectHash] = (string) $data;
}
if ($this->options['debugInfo'] && method_exists($data, '__debugInfo')) {
$data = (array) $data->__debugInfo();
} elseif ($this->options['jsonSerialize'] && method_exists($data, 'jsonSerialize')) {
$data = (array) $data->jsonSerialize();
} elseif ($this->options['toArray'] && method_exists($data, 'toArray')) {
$data = (array) $data->toArray();
} else {
$data = (array) $data;
}
$data = array_combine(
array_map(function ($key) {
// replace null-byte prefixes of protected and private properties used by php with * (protected)
// and ~ (private)
return preg_replace('/^\0.+?\0/', '~', str_replace("\0*\0", '*', $key));
}, array_keys($data)),
$this->normalizeEach($data, $context, $limit - 1)
);
return $this->cache[$objectHash] = [ '__class__' => $className ] + $data;
} elseif (is_resource($data)) {
return [ '__type__' => 'resource' ];
}
return $data;
}
// Normalize each member of an array (doesn't add metadata for top level)
public function normalizeEach($data, $context = null, $limit = null) {
return array_map(function ($item) use ($context, $limit) {
return $this->normalize($item, $context, $limit);
}, $data);
}
// Normalize a stack trace instance
public function trace(StackTrace $trace)
{
if (! $this->options['traces']) return null;
if ($this->options['tracesFilter']) $trace = $trace->filter($this->options['tracesFilter']);
if ($this->options['tracesSkip']) $trace = $trace->skip($this->options['tracesSkip']);
if ($this->options['tracesLimit']) $trace = $trace->limit($this->options['tracesLimit']);
return array_map(function ($frame) {
return [
'call' => $frame->call,
'file' => $frame->file,
'line' => $frame->line,
'isVendor' => (bool) $frame->vendor
];
}, $trace->frames());
}
// Normalize an exception instance
public function exception(/* Throwable */ $exception)
{
return [
'type' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => (new Serializer([ 'tracesLimit' => false ]))->trace(StackTrace::from($exception->getTrace())),
'previous' => $exception->getPrevious() ? $this->exception($exception->getPrevious()) : null
];
}
}

View File

@@ -0,0 +1,45 @@
<?php namespace Clockwork\Helpers;
use Clockwork\Request\Request;
// Generates Server-Timing header value
class ServerTiming
{
// Performance metrics to include
protected $metrics = [];
// Add a performance metric
public function add($metric, $value, $description)
{
$this->metrics[] = [ 'metric' => $metric, 'value' => $value, 'description' => $description ];
return $this;
}
// Generate the header value
public function value()
{
return implode(', ', array_map(function ($metric) {
return "{$metric['metric']}; dur={$metric['value']}; desc=\"{$metric['description']}\"";
}, $this->metrics));
}
// Create a new instance from a Clockwork request
public static function fromRequest(Request $request, $eventsCount = 10)
{
$header = new static;
$header->add('app', $request->getResponseDuration(), 'Application');
if ($request->getDatabaseDuration()) {
$header->add('db', $request->getDatabaseDuration(), 'Database');
}
// add timeline events limited to a set number so the header doesn't get too large
foreach (array_slice($request->timeline()->events, 0, $eventsCount) as $i => $event) {
$header->add("timeline-event-{$i}", $event->duration(), $event->description);
}
return $header;
}
}

View File

@@ -0,0 +1,148 @@
<?php namespace Clockwork\Helpers;
// Filter stack traces
class StackFilter
{
protected $classes = [];
protected $notClasses = [];
protected $files = [];
protected $notFiles = [];
protected $functions = [];
protected $notFunctions = [];
protected $namespaces = [];
protected $notNamespaces = [];
protected $vendors = [];
protected $notVendors = [];
public static function make()
{
return new static;
}
public function isClass($classes)
{
$this->classes = array_merge($this->classes, is_array($classes) ? $classes : [ $classes ]);
return $this;
}
public function isNotClass($classes)
{
$this->notClasses = array_merge($this->notClasses, is_array($classes) ? $classes : [ $classes ]);
return $this;
}
public function isFile($files)
{
$this->files = array_merge($this->files, is_array($files) ? $files : [ $files ]);
return $this;
}
public function isNotFile($files)
{
$this->notFiles = array_merge($this->notFiles, is_array($files) ? $files : [ $files ]);
return $this;
}
public function isFunction($functions)
{
$this->functions = array_merge($this->functions, is_array($functions) ? $functions : [ $functions ]);
return $this;
}
public function isNotFunction($functions)
{
$this->notFunctions = array_merge($this->notFunctions, is_array($functions) ? $functions : [ $functions ]);
return $this;
}
public function isNamespace($namespaces)
{
$this->namespaces = array_merge($this->namespaces, is_array($namespaces) ? $namespaces : [ $namespaces ]);
return $this;
}
public function isNotNamespace($namespaces)
{
$this->notNamespaces = array_merge($this->notNamespaces, is_array($namespaces) ? $namespaces : [ $namespaces ]);
return $this;
}
public function isVendor($vendors)
{
$this->vendors = array_merge($this->vendors, is_array($vendors) ? $vendors : [ $vendors ]);
return $this;
}
public function isNotVendor($vendors)
{
$this->notVendors = array_merge($this->notVendors, is_array($vendors) ? $vendors : [ $vendors ]);
return $this;
}
// Apply the filter to a stack frame
public function filter(StackFrame $frame)
{
return $this->matchesClass($frame)
&& $this->matchesFile($frame)
&& $this->matchesFunction($frame)
&& $this->matchesNamespace($frame)
&& $this->matchesVendor($frame);
}
// Return a closure calling this filter
public function closure()
{
return function ($frame) { return $this->filter($frame); };
}
protected function matchesClass(StackFrame $frame)
{
if (count($this->classes) && ! in_array($frame->class, $this->classes)) return false;
if (count($this->notClasses) && in_array($frame->class, $this->notClasses)) return false;
return true;
}
protected function matchesFile(StackFrame $frame)
{
if (count($this->files) && ! in_array($frame->file, $this->files)) return false;
if (count($this->notFiles) && in_array($frame->file, $this->notFiles)) return false;
return true;
}
protected function matchesFunction(StackFrame $frame)
{
if (count($this->functions) && ! in_array($frame->function, $this->functions)) return false;
if (count($this->notFunctions) && in_array($frame->function, $this->notFunctions)) return false;
return true;
}
protected function matchesNamespace(StackFrame $frame)
{
foreach ($this->notNamespaces as $namespace) {
if ($frame->class !== null && strpos($frame->class, "{$namespace}\\") !== false) return false;
}
if (! count($this->namespaces)) return true;
foreach ($this->namespaces as $namespace) {
if ($frame->class !== null && strpos($frame->class, "{$namespace}\\") !== false) return true;
}
return false;
}
protected function matchesVendor(StackFrame $frame)
{
if (count($this->vendors) && ! in_array($frame->vendor, $this->vendors)) return false;
if (count($this->notVendors) && in_array($frame->vendor, $this->notVendors)) return false;
return true;
}
}

View File

@@ -0,0 +1,38 @@
<?php namespace Clockwork\Helpers;
// A single frame of a stack trace
class StackFrame
{
public $call;
public $function;
public $line;
public $file;
public $class;
public $object;
public $type;
public $args = [];
public $shortPath;
public $vendor;
public function __construct(array $data = [], $basePath = '', $vendorPath = '')
{
foreach ($data as $key => $value) {
$this->$key = $value;
}
$this->call = $this->formatCall();
$this->shortPath = $this->file ? str_replace($basePath, '', $this->file) : null;
$this->vendor = ($this->file && strpos($this->file, $vendorPath) === 0)
? explode(DIRECTORY_SEPARATOR, str_replace($vendorPath, '', $this->file))[0] : null;
}
protected function formatCall()
{
if ($this->class) {
return "{$this->class}{$this->type}{$this->function}()";
} else {
return "{$this->function}()";
}
}
}

View File

@@ -0,0 +1,127 @@
<?php namespace Clockwork\Helpers;
// A stack trace
class StackTrace
{
use Concerns\ResolvesViewName;
protected $frames;
protected $basePath;
protected $vendorPath;
// Capture a new stack trace, accepts an array of options, "arguments" to include arguments in the trace and "limit"
// to limit the trace length
public static function get($options = [])
{
$backtraceOptions = isset($options['arguments'])
? DEBUG_BACKTRACE_PROVIDE_OBJECT : DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS;
$limit = isset($options['limit']) ? $options['limit'] : 0;
return static::from(debug_backtrace($backtraceOptions, $limit));
}
// Create a stack trace from an existing debug_backtrace output
public static function from(array $trace)
{
$basePath = static::resolveBasePath();
$vendorPath = static::resolveVendorPath();
return new static(array_map(function ($frame, $index) use ($basePath, $vendorPath, $trace) {
return new StackFrame(
static::fixCallUserFuncFrame($frame, $trace, $index), $basePath, $vendorPath
);
}, $trace, array_keys($trace)), $basePath, $vendorPath);
}
public function __construct(array $frames, $basePath, $vendorPath)
{
$this->frames = $frames;
$this->basePath = $basePath;
$this->vendorPath = $vendorPath;
}
// Get all frames
public function frames()
{
return $this->frames;
}
// Get the first frame, optionally filtered by a stack filter or a closure
public function first($filter = null)
{
if (! $filter) return reset($this->frames);
if ($filter instanceof StackFilter) $filter = $filter->closure();
foreach ($this->frames as $frame) {
if ($filter($frame)) return $frame;
}
}
// Get the last frame, optionally filtered by a stack filter or a closure
public function last($filter = null)
{
if (! $filter) return $this->frames[count($this->frames) - 1];
if ($filter instanceof StackFilter) $filter = $filter->closure();
foreach (array_reverse($this->frames) as $frame) {
if ($filter($frame)) return $frame;
}
}
// Get trace filtered by a stack filter or a closure
public function filter($filter = null)
{
if ($filter instanceof StackFilter) $filter = $filter->closure();
return $this->copy(array_values(array_filter($this->frames, $filter)));
}
// Get trace skipping a number of frames or frames matching a stack filter or a closure
public function skip($count = null)
{
if ($count instanceof StackFilter) $count = $count->closure();
if ($count instanceof \Closure) $count = array_search($this->first($count), $this->frames);
return $this->copy(array_slice($this->frames, $count));
}
// Get trace with a number of frames from the top
public function limit($count = null)
{
return $this->copy(array_slice($this->frames, 0, $count));
}
// Get a copy of the trace
public function copy($frames = null)
{
return new static($frames ?: $this->frames, $this->basePath, $this->vendorPath);
}
protected static function resolveBasePath()
{
return substr(__DIR__, 0, strpos(__DIR__, DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR));
}
protected static function resolveVendorPath()
{
return static::resolveBasePath() . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR;
}
// Fixes call_user_func stack frames missing file and line
protected static function fixCallUserFuncFrame($frame, array $trace, $index)
{
if (isset($frame['file'])) return $frame;
$nextFrame = isset($trace[$index + 1]) ? $trace[$index + 1] : null;
if (! $nextFrame || ! in_array($nextFrame['function'], [ 'call_user_func', 'call_user_func_array' ])) return $frame;
$frame['file'] = $nextFrame['file'];
$frame['line'] = $nextFrame['line'];
return $frame;
}
}