Files
faveo/vendor/itsgoingd/clockwork/Clockwork/Request/Request.php
2023-06-08 18:56:00 +05:30

592 lines
19 KiB
PHP

<?php namespace Clockwork\Request;
use Clockwork\Helpers\Serializer;
// Data structure representing a single application request
class Request
{
// Unique request ID
public $id;
// Metadata version
public $version = 1;
// Request type (request, command, queue-job or test)
public $type = 'request';
// Request time
public $time;
// Request method
public $method;
// Request URL
public $url;
// Request URI
public $uri;
// Request headers
public $headers = [];
// Textual representation of the executed controller
public $controller;
// Request GET data
public $getData = [];
// Request POST data
public $postData = [];
// Request body data
public $requestData = [];
// Session data array
public $sessionData = [];
// Authenticated user
public $authenticatedUser;
// Request cookies
public $cookies = [];
// Response time
public $responseTime;
// Response processing time
public $responseDuration;
// Response status code
public $responseStatus;
// Peak memory usage in bytes
public $memoryUsage;
// Executed middleware
public $middleware = [];
// Database queries
public $databaseQueries = [];
// Database queries count
public $databaseQueriesCount;
// Database slow queries count
public $databaseSlowQueries;
// Database query counts of a particular type (selects, inserts, updates, deletes, others)
public $databaseSelects;
public $databaseInserts;
public $databaseUpdates;
public $databaseDeletes;
public $databaseOthers;
public $databaseDuration;
// Cache queries
public $cacheQueries = [];
// Cache query counts of a particular type (reads, hits, writes, deletes)
public $cacheReads;
public $cacheHits;
public $cacheWrites;
public $cacheDeletes;
// Cache queries execution time
public $cacheTime;
// Model actions
public $modelsActions = [];
// Model action counts by model
public $modelsRetrieved = [];
public $modelsCreated = [];
public $modelsUpdated = [];
public $modelsDeleted = [];
// Redis commands
public $redisCommands = [];
// Dispatched queue jobs
public $queueJobs = [];
// Timeline events
public $timelineData = [];
// Log messages
public $log = [];
// Fired events
public $events = [];
// Application routes
public $routes = [];
// Sent notifications
public $notifications = [];
// Sent emails (legacy property replaced by notifications)
public $emailsData = [];
// Rendered views
public $viewsData = [];
// Custom user data
public $userData = [];
// Subrequests
public $subrequests = [];
// Xebug profiler data
public $xdebug = [];
// Command name
public $commandName;
// Command arguments passed in
public $commandArguments = [];
// Command arguments defaults
public $commandArgumentsDefaults = [];
// Command options passed in
public $commandOptions = [];
// Command options defaults
public $commandOptionsDefaults = [];
// Command exit code
public $commandExitCode;
// Command output
public $commandOutput;
// Queue job name
public $jobName;
// Queue job description
public $jobDescription;
// Queue job status
public $jobStatus;
// Queue job payload
public $jobPayload = [];
// Queue job queue name
public $jobQueue;
// Queue job connection name
public $jobConnection;
// Queue job additional options
public $jobOptions = [];
// Test name
public $testName;
// Test status
public $testStatus;
// Test status message (eg. in case of failure)
public $testStatusMessage;
// Ran test asserts
public $testAsserts = [];
// Client-side performance metrics in the form of [ metric => value ]
public $clientMetrics = [];
// Web vitals in the form of [ vital => value ]
public $webVitals = [];
// Parent request
public $parent;
// Token to update this request data
public $updateToken;
// Log instance for the current request
protected $currentLog;
// Timeline instance for the current request
protected $currentTimeline;
// Array of property values to override collected values from data sources
protected $overrides = [];
// Create a new request, if optional data array argument is provided, it will be used to populate the request object,
// otherwise an empty request with current time, autogenerated ID and update token will be created
public function __construct(array $data = [])
{
$this->id = isset($data['id']) ? $data['id'] : $this->generateRequestId();
$this->time = microtime(true);
$this->updateToken = isset($data['updateToken']) ? $data['updateToken'] : $this->generateUpdateToken();
foreach ($data as $key => $val) $this->$key = $val;
$this->currentLog = new Log($this->log);
$this->currentTimeline = new Timeline\Timeline($this->timelineData);
}
// Compute the sum of durations of all database queries
public function getDatabaseDuration()
{
return array_reduce($this->databaseQueries, function ($total, $query) {
return isset($query['duration']) ? $total + $query['duration'] : $total;
}, 0);
}
// Compute response duration in milliseconds
public function getResponseDuration()
{
return ($this->responseTime - $this->time) * 1000;
}
// Get all request data as an array
public function toArray()
{
return [
'id' => $this->id,
'version' => $this->version,
'type' => $this->type,
'time' => $this->time,
'method' => $this->method,
'url' => $this->url,
'uri' => $this->uri,
'headers' => $this->headers,
'controller' => $this->controller,
'getData' => $this->getData,
'postData' => $this->postData,
'requestData' => $this->requestData,
'sessionData' => $this->sessionData,
'authenticatedUser' => $this->authenticatedUser,
'cookies' => $this->cookies,
'responseTime' => $this->responseTime,
'responseStatus' => $this->responseStatus,
'responseDuration' => $this->responseDuration ?: $this->getResponseDuration(),
'memoryUsage' => $this->memoryUsage,
'middleware' => $this->middleware,
'databaseQueries' => $this->databaseQueries,
'databaseQueriesCount' => $this->databaseQueriesCount,
'databaseSlowQueries' => $this->databaseSlowQueries,
'databaseSelects' => $this->databaseSelects,
'databaseInserts' => $this->databaseInserts,
'databaseUpdates' => $this->databaseUpdates,
'databaseDeletes' => $this->databaseDeletes,
'databaseOthers' => $this->databaseOthers,
'databaseDuration' => $this->getDatabaseDuration(),
'cacheQueries' => $this->cacheQueries,
'cacheReads' => $this->cacheReads,
'cacheHits' => $this->cacheHits,
'cacheWrites' => $this->cacheWrites,
'cacheDeletes' => $this->cacheDeletes,
'cacheTime' => $this->cacheTime,
'modelsActions' => $this->modelsActions,
'modelsRetrieved' => $this->modelsRetrieved,
'modelsCreated' => $this->modelsCreated,
'modelsUpdated' => $this->modelsUpdated,
'modelsDeleted' => $this->modelsDeleted,
'redisCommands' => $this->redisCommands,
'queueJobs' => $this->queueJobs,
'timelineData' => $this->timeline()->toArray(),
'log' => $this->log()->toArray(),
'events' => $this->events,
'routes' => $this->routes,
'notifications' => $this->notifications,
'emailsData' => $this->emailsData,
'viewsData' => $this->viewsData,
'userData' => array_map(function ($data) {
return $data instanceof UserData ? $data->toArray() : $data;
}, $this->userData),
'subrequests' => $this->subrequests,
'xdebug' => $this->xdebug,
'commandName' => $this->commandName,
'commandArguments' => $this->commandArguments,
'commandArgumentsDefaults' => $this->commandArgumentsDefaults,
'commandOptions' => $this->commandOptions,
'commandOptionsDefaults' => $this->commandOptionsDefaults,
'commandExitCode' => $this->commandExitCode,
'commandOutput' => $this->commandOutput,
'jobName' => $this->jobName,
'jobDescription' => $this->jobDescription,
'jobStatus' => $this->jobStatus,
'jobPayload' => $this->jobPayload,
'jobQueue' => $this->jobQueue,
'jobConnection' => $this->jobConnection,
'jobOptions' => $this->jobOptions,
'testName' => $this->testName,
'testStatus' => $this->testStatus,
'testStatusMessage' => $this->testStatusMessage,
'testAsserts' => $this->testAsserts,
'clientMetrics' => $this->clientMetrics,
'webVitals' => $this->webVitals,
'parent' => $this->parent,
'updateToken' => $this->updateToken
];
}
// Get all request data as a JSON string
public function toJson()
{
return json_encode($this->toArray(), \JSON_PARTIAL_OUTPUT_ON_ERROR);
}
// Return request data except specified keys as an array
public function except($keys)
{
return array_filter($this->toArray(), function ($value, $key) use ($keys) {
return ! in_array($key, $keys);
}, ARRAY_FILTER_USE_BOTH);
}
// Return only request data with specified keys as an array
public function only($keys)
{
return array_filter($this->toArray(), function ($value, $key) use ($keys) {
return in_array($key, $keys);
}, ARRAY_FILTER_USE_BOTH);
}
// Return log instance for the current request
public function log()
{
return $this->currentLog;
}
// Return timeline instance for the current request
public function timeline()
{
return $this->currentTimeline;
}
// Add a new overridden property
public function override($property, $value)
{
$this->overrides[$property] = $value;
return $this;
}
// Get or set all overrides at once
public function overrides($overrides = null)
{
if (! $overrides) return $this->overrides;
$this->overrides = $overrides;
return $this;
}
// Add database query, takes query, bindings, duration (in ms) and additional data - connection (connection name),
// time (when was the query executed), file (caller file name), line (caller line number), trace (serialized trace),
// model (associated ORM model)
public function addDatabaseQuery($query, $bindings = [], $duration = null, $data = [])
{
$this->databaseQueries[] = [
'query' => $query,
'bindings' => (new Serializer)->normalize($bindings),
'duration' => $duration,
'connection' => isset($data['connection']) ? $data['connection'] : null,
'time' => isset($data['time']) ? $data['time'] : microtime(true) - ($duration ?: 0) / 1000,
'file' => isset($data['file']) ? $data['file'] : null,
'line' => isset($data['line']) ? $data['line'] : null,
'trace' => isset($data['trace']) ? $data['trace'] : null,
'model' => isset($data['model']) ? $data['model'] : null,
'tags' => array_merge(
isset($data['tags']) ? $data['tags'] : [], isset($data['slow']) ? [ 'slow' ] : []
)
];
}
// Add model action, takes model, action and additional data - key, attributes, changes, time (when was the action
// executed), query, duration (in ms), connection (connection name), trace (serialized trace), file (caller file
// name), line (caller line number), tags
public function addModelAction($model, $action, $data = [])
{
$this->modelActions[] = [
'model' => $model,
'key' => isset($data['key']) ? $data['key'] : null,
'action' => $action,
'attributes' => isset($data['attributes']) ? $data['attributes'] : [],
'changes' => isset($data['changes']) ? $data['changes'] : [],
'duration' => $duration = isset($data['duration']) ? $data['duration'] : null,
'time' => isset($data['time']) ? $data['time'] : microtime(true) - ($duration ?: 0) / 1000,
'query' => isset($data['query']) ? $data['query'] : null,
'connection' => isset($data['connection']) ? $data['connection'] : null,
'trace' => isset($data['trace']) ? $data['trace'] : null,
'file' => isset($data['file']) ? $data['file'] : null,
'line' => isset($data['line']) ? $data['line'] : null,
'tags' => isset($data['tags']) ? $data['tags'] : []
];
}
// Add cache query, takes type, key, value, duration (in ms) and additional data - connection (connection name),
// time (when was the query executed), file (caller file name), line (caller line number), trace (serialized trace),
// expiration
public function addCacheQuery($type, $key, $value = null, $duration = null, $data = [])
{
$this->cacheQueries[] = [
'type' => $type,
'key' => $key,
'value' => (new Serializer)->normalize($value),
'duration' => $duration,
'connection' => isset($data['connection']) ? $data['connection'] : null,
'time' => isset($data['time']) ? $data['time'] : microtime(true) - ($duration ?: 0) / 1000,
'file' => isset($data['file']) ? $data['file'] : null,
'line' => isset($data['line']) ? $data['line'] : null,
'trace' => isset($data['trace']) ? $data['trace'] : null,
'expiration' => isset($data['expiration']) ? $data['expiration'] : null
];
}
// Add event, takes event name, data, time and additional data - listeners, duration (in ms), file (caller file
// name), line (caller line number), trace (serialized trace)
public function addEvent($event, $eventData = null, $time = null, $data = [])
{
$this->events[] = [
'event' => $event,
'data' => (new Serializer)->normalize($eventData),
'duration' => $duration = isset($data['duration']) ? $data['duration'] : null,
'time' => $time ? $time : microtime(true) - ($duration ?: 0) / 1000,
'listeners' => isset($data['listeners']) ? $data['listeners'] : null,
'file' => isset($data['file']) ? $data['file'] : null,
'line' => isset($data['line']) ? $data['line'] : null,
'trace' => isset($data['trace']) ? $data['trace'] : null
];
}
// Add route, takes method, uri, action and additional data - name, middleware, before (before filters), after
// (after filters)
public function addRoute($method, $uri, $action, $data = [])
{
$this->routes[] = [
'method' => $method,
'uri' => $uri,
'action' => $action,
'name' => isset($data['name']) ? $data['name'] : null,
'middleware' => isset($data['middleware']) ? $data['middleware'] : null,
'before' => isset($data['before']) ? $data['before'] : null,
'after' => isset($data['after']) ? $data['after'] : null
];
}
// Add sent notifucation, takes subject, recipient, sender, and additional data - time, duration, type, content, data
public function addNotification($subject, $to, $from = null, $data = [])
{
$this->notifications[] = [
'subject' => $subject,
'from' => $from,
'to' => $to,
'content' => isset($data['content']) ? $data['content'] : null,
'type' => isset($data['type']) ? $data['type'] : null,
'data' => isset($data['data']) ? $data['data'] : [],
'duration' => $duration = isset($data['duration']) ? $data['duration'] : null,
'time' => isset($data['time']) ? $data['time'] : microtime(true) - ($duration ?: 0) / 1000,
'trace' => isset($data['trace']) ? $data['trace'] : null,
'file' => isset($data['file']) ? $data['file'] : null,
'line' => isset($data['line']) ? $data['line'] : null
];
}
// Add sent email, takes subject, recipient address, sender address, array of headers, and additional data - time
// (when was the email sent), duration (sending time in ms)
public function addEmail($subject, $to, $from = null, $headers = [], $data = [])
{
$this->emailsData[] = [
'start' => isset($data['time']) ? $data['time'] : null,
'end' => isset($data['time'], $data['duration']) ? $data['time'] + $data['duration'] / 1000 : null,
'duration' => isset($data['duration']) ? $data['duration'] : null,
'description' => 'Sending an email message',
'data' => [
'subject' => $subject,
'to' => $to,
'from' => $from,
'headers' => (new Serializer)->normalize($headers)
]
];
}
// Add view, takes view name, view data and additional data - time (when was the view rendered), duration (sending
// time in ms)
public function addView($name, $viewData = [], $data = [])
{
$this->viewsData[] = [
'start' => isset($data['time']) ? $data['time'] : null,
'end' => isset($data['time'], $data['duration']) ? $data['time'] + $data['duration'] / 1000 : null,
'duration' => isset($data['duration']) ? $data['duration'] : null,
'description' => 'Rendering a view',
'data' => [
'name' => $name,
'data' => (new Serializer)->normalize($viewData)
]
];
}
// Add executed subrequest, takes the requested url, subrequest Clockwork ID and additional data - path if non-default
public function addSubrequest($url, $id, $data = [])
{
$this->subrequests[] = [
'url' => $url,
'id' => $id,
'path' => isset($data['path']) ? $data['path'] : null
];
}
// Set the authenticated user, takes a username, an id and additional data - email and name
public function setAuthenticatedUser($username, $id = null, $data = [])
{
$this->authenticatedUser = [
'id' => $id,
'username' => $username,
'email' => isset($data['email']) ? $data['email'] : null,
'name' => isset($data['name']) ? $data['name'] : null
];
}
// Set parent request, takes the request id and additional options - url and path if non-default
public function setParent($id, $data = [])
{
$this->parent = [
'id' => $id,
'url' => isset($data['url']) ? $data['url'] : null,
'path' => isset($data['path']) ? $data['path'] : null
];
}
// Add custom user data
public function userData($key = null)
{
if ($key && isset($this->userData[$key])) {
return $this->userData[$key];
}
$userData = (new UserData)->title($key);
return $key ? $this->userData[$key] = $userData : $this->userData[] = $userData;
}
// Add a ran test assert, takes the assert name, arguments, whether it passed and trace as arguments
public function addTestAssert($name, $arguments = null, $passed = true, $trace = null)
{
$this->testAsserts[] = [
'name' => $name,
'arguments' => (new Serializer)->normalize($arguments),
'trace' => $trace,
'passed' => $passed
];
}
// Generate unique request ID in the form of <current time>-<random number>
protected function generateRequestId()
{
return str_replace('.', '-', sprintf('%.4F', microtime(true))) . '-' . mt_rand();
}
// Generate a random update token
protected function generateUpdateToken()
{
$length = 8;
$bytes = function_exists('random_bytes') ? random_bytes($length) : openssl_random_pseudo_bytes($length);
return substr(bin2hex($bytes), 0, $length);
}
}