clock-work
This commit is contained in:
9
vendor/itsgoingd/clockwork/.editorconfig
vendored
Normal file
9
vendor/itsgoingd/clockwork/.editorconfig
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
|
||||
[*.php]
|
||||
insert_final_newline = true
|
1
vendor/itsgoingd/clockwork/.gitattributes
vendored
Normal file
1
vendor/itsgoingd/clockwork/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.github/ export-ignore
|
706
vendor/itsgoingd/clockwork/CHANGELOG.md
vendored
Normal file
706
vendor/itsgoingd/clockwork/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,706 @@
|
||||
5.1.12
|
||||
|
||||
- improved Timeline event run method to stop the event in case of an exception (implemented by UlrichEckhardt, thanks!)
|
||||
- fixed some deprecation warnings on PHP 8.2 (implemented by faytekin, thanks!)
|
||||
- fixed some deprecation warnings on PHP 8.1 (implemented by villermen, thanks!)
|
||||
|
||||
5.1.11
|
||||
|
||||
- fixed crash when resolving authenticated user in Laravel without using Eloquent (reported by m-thalmann-athesia, thanks!)
|
||||
|
||||
5.1.10
|
||||
|
||||
- fixed crash when resolving authenticated user in Laravel (reported by LucaRed, thanks!)
|
||||
|
||||
5.1.9
|
||||
|
||||
- added support for Eloquent strict mode (reported by Sergiobop, thanks!)
|
||||
|
||||
5.1.8
|
||||
|
||||
- updated list of built-in Laravel commands to ignore when collecting commands and included Horizon commands
|
||||
- fixed collecting of Laravel queue jobs when used with Horizon
|
||||
- fixed collecting of authanticated user name when the User model includes name() method (implemented by devfrey, thanks!)
|
||||
|
||||
5.1.7
|
||||
|
||||
- added support for authentiaction in the Vanilla integration
|
||||
- added support for compressed Xdebug profiles
|
||||
- improved collecting of Laravel Artisan commands to support abbreviated commands (implemented by mike-peters90, thanks!)
|
||||
- fixed doubled backslashes in collected Laravel database query bindings (reported by pys1992, thanks!)
|
||||
- fixed compatibility with PostgreSQL in SQL storage (implemented by screw, thanks!)
|
||||
- fixed possible crash during file storage cleanup when used with Laravel Octane (reported by flexchar, thanks!)
|
||||
- fixed infinite loop when collecting queries in Doctrine 3.x (reported by N-M, thanks!)
|
||||
|
||||
5.1.6
|
||||
|
||||
- added Monolog 2.x compatible handler (idea by mahagr, thanks!)
|
||||
- improved log to handle all Throwable classes as exceptions (idea by EdmondDantes, thanks!)
|
||||
- fixed support for capturing console output in Laravel 9 (reported by mikerockett, thanks!)
|
||||
|
||||
5.1.5
|
||||
|
||||
- removed support for psr/log
|
||||
- fixed some typos (implemented by fridzema, thanks!)
|
||||
|
||||
*BREAKING*
|
||||
|
||||
- `Clockwork\Request\Log` no longer implements the PSR log interface, it is unlikely you are using this class directly
|
||||
|
||||
5.1.4
|
||||
|
||||
- added Laravel 9 support
|
||||
- added support for manually registering Clockwork middleware in Laravel
|
||||
- fixed some failing tests might not been collected in Laravel (reported by ajcastro, thanks!)
|
||||
- fixed not respecting the collect tests setting in Laravel (reported by SimBig, thanks!)
|
||||
- fixed some deprecation warnings on PHP 8.1 (implemented by usernotnull, thanks!)
|
||||
|
||||
5.1.3
|
||||
|
||||
- added PSR to the default filtered namespaces from stack traces in the Laravel integration
|
||||
- fixed not being able to log non-string values when using psr/log >=2.0 (reported by Wit3, thanks!)
|
||||
- fixed some deprecation warnings on PHP 8.1 (reported by Pinnokkio, thanks!)
|
||||
- fixed wrong redirect when accessing web ui with an url including a trailing slash (implemented by ssnepenthe, thanks!)
|
||||
- fixed update-token could be leaked via the Clockwork rest api (implemented by ssnepenthe, thanks!)
|
||||
|
||||
5.1.2
|
||||
|
||||
- fixed some deprecation warnings on PHP 8.1 (reported by Codomic, thanks!)
|
||||
|
||||
5.1.1
|
||||
|
||||
- added support for psr/log 2.0 (used in recent Laravel versions) (implemented by byGits, thanks!)
|
||||
- improved timeline api event run method to return the return value of passed closure
|
||||
- improved collecting Laravel database queries to not quote integers (implemented by thisiskj, thanks!)
|
||||
- improved toolbar details link to always be absolute and work with subdirectories (reported by superDuperCyberTechno, thanks!)
|
||||
- fixed some deprecation warnings on PHP 8.1 (implemented by gharlan, thanks!)
|
||||
- fixed collecting Laravel database queries to produce correct queries when bindings contain question marks (reported by woshixiaobai, thanks!)
|
||||
- fixed filtering collected and recorded requests by closure (implemented by ssnepenthe, thanks!)
|
||||
- fixed some inconsistencies in the Clockwork metadata api
|
||||
- fixed some web UI assets being server with wrong mime-types (implemented by ssnepenthe, thanks!)
|
||||
- fixed missing method on storage interface and missing default parameter value in sql storage (implemented by ssnepenthe, thanks!)
|
||||
|
||||
*BREAKING*
|
||||
|
||||
- timeline api event run method now returns the return value of passed closure instead of the event instance
|
||||
|
||||
5.1
|
||||
|
||||
- added initial support for Laravel Octane
|
||||
- added support for Web UI in the vanilla integration
|
||||
- added support for collecting Laravel cache queries without values (implemented by akalongman, thanks!)
|
||||
- added ability to filter Laravel routes from particular namespaces (idea by hailwood, thanks!)
|
||||
- improved collecting of request URL to include full URL including the query string
|
||||
- improved Clockwork Browser payload to include Web UI path
|
||||
- updated Clockwork App (5.1)
|
||||
- fixed logging falsy values via Clockwork::log (reported by Karmalakas, thanks!)
|
||||
- fixed PHP 8 incompatibility when processing some Laravel notifications (implemented by nanaya, thanks!)
|
||||
- fixed request body being collected even when already parsed into POST data
|
||||
- fixed collecting request URLs with non-standard ports
|
||||
|
||||
5.0.8
|
||||
|
||||
- fixed crash when collecting Laravel mailables built via MailMessage (implemented by cbl, thanks!)
|
||||
- fixed crash when collecting artisan command in Lumen (reported by 2Attack, thanks!)
|
||||
- fixed crash when collecting database queries in Laravel with connection implementation not using PDO (implemented by lenssoft, thanks!)
|
||||
- fixed crash when HTTP request body contains valid json which does not contain array (eg. a number) (reported by Mradxz, thanks!)
|
||||
- fixed collected jobs dispatched from other jobs not having a correct parent job set (implemented by josvar, thanks!)
|
||||
|
||||
5.0.7
|
||||
|
||||
- changed delay listening to events until the app is booted (improves comatibility with some other packages)
|
||||
- changed default settings to enable toolbar (separately installed component)
|
||||
- changed default except requests filter to include debugbar api (implemented by edgardmessias, thanks!)
|
||||
- fixed wrong type-hint for the timeline event run method (reported by hferradj, thanks!)
|
||||
- fixed on-demand mode not working in Laravel (reported by yemenifree, thanks!)
|
||||
- fixed crash when collecting Laravel notifications with recipient names (reported by iainheng, thanks!)
|
||||
- fixed possible crashes and other issues when collecting Laravel notifications (reported by beekmanbv, thanks!)
|
||||
- fixed crash when creating runnable queries in DBAL data source (implemented by N-M, thanks!)
|
||||
|
||||
5.0.6
|
||||
|
||||
- fixed vanilla integration overriding other cookies when used with a PSR-7 response (reported by leemason, thanks!)
|
||||
|
||||
5.0.5
|
||||
|
||||
- added support for toolbar in the vanilla integration (idea by reeslo, thanks!)
|
||||
- added support for client metrics in the vanilla integration
|
||||
- improved PSR-7 support in the vanilla integration
|
||||
- fixed toolbar might not work when not collecting database models
|
||||
- fixed crash collecting Slack and Nexmo notifications (reported by abalozz, thanks!)
|
||||
- fixed timeline api usage not being updated in the Slim integration leading to crash (reported by jiaojie1989, implemented by seanhamlin, thanks!)
|
||||
- fixed api path being interpreted as regex in the vanilla integration (implemented by pqr, thanks!)
|
||||
- fixed Symfony storage not being updated for latest storage api (implemented by auchanhub, thanks!)
|
||||
|
||||
5.0.4
|
||||
|
||||
- fixed Lumen integration crash (implemented by alexbegoon, thanks!)
|
||||
- fixed PHP 5.6 incompatibility (implemented by sanis, thanks!)
|
||||
|
||||
5.0.3
|
||||
|
||||
- fixed PHP 8.0 incompatibility in log (implemented by mtorromeo, thanks!)
|
||||
|
||||
5.0.2
|
||||
|
||||
- fixed data sources not being initialized for extended data requests (reported by tmishutin, thanks!)
|
||||
- fixed inconsistent handling of time and duration arguments in various Request::add* methods (reported by mahagr, thanks!)
|
||||
- updated Clockwork App (5.0.2)
|
||||
|
||||
5.0.1
|
||||
|
||||
- fixed performance issues related to collecting stack traces for Eloquent models actions (reported by mstaack, thanks!)
|
||||
- fixed collecting database and unsupported Laravel notifications (implemented by YannikFirre, thanks!)
|
||||
- fixed log and timeline sorting leading to invalid metadata format
|
||||
- updated Clockwork App (5.0.1)
|
||||
|
||||
5.0
|
||||
|
||||
- added collecting of client-metrics and web-vitals
|
||||
- added collecting of Eloquent models actions and retrieved, created, updated and deleted models counts
|
||||
- added collecting of Laravel notifications
|
||||
- added reworked timeline api
|
||||
- added configurable web ui path (default changed to /clockwork)
|
||||
- added toolbar support
|
||||
- added on-demand mode (with optional secret)
|
||||
- added option to collect error requests only (requests with 4xx and 5xx responses)
|
||||
- added option to specify slow threshold and collect slow requests only
|
||||
- added option to sample collected requests (collect only 1 in x requests)
|
||||
- added option to collect only specified urls
|
||||
- added option to not collect pre-flight requests (enabled by default)
|
||||
- added option to filter collected and recorded requests by closure
|
||||
- added Laravel controller timeline event
|
||||
- added support for updating existing requests
|
||||
- added Slim 4 support
|
||||
- updated to Clockwork App 5.0
|
||||
- improved reworked the central Clockwork class api
|
||||
- improved requests recording to use a terminate callback
|
||||
- improved global log instance to live on the request instance
|
||||
- improved global timeline instance to live on the request instance
|
||||
- improved Symfony routes registration to register web ui paths only when enabled
|
||||
- improved SQL storage to be more compatible with different PDO error modes
|
||||
- improved Clockwork rest api with only/except filters
|
||||
- improved handling of corrupted index records in file storage
|
||||
- improved cleaned up the code-base, added and improved comments, use modern php features
|
||||
- removed Laravel total, initialization, boot and run timeline events
|
||||
- removed legacy clockwork.controller events
|
||||
- removed duplicate file/line information from collected metadata
|
||||
- fixed authentication route not being registered when web ui is disabled
|
||||
- fixed database queries not being collected for queue jobs
|
||||
- fixed multi-line database queries not being counted properly (implemented by edgardmessias, thanks!)
|
||||
- fixed StackFrame not processing Windows paths correctly
|
||||
|
||||
*BREAKING*
|
||||
|
||||
- multiple changes to the Laravel config file, please review and re-publish
|
||||
- minimal required PHP version is now 5.6 (previously 5.5)
|
||||
- the timeline api was reworked, please see documentation for details
|
||||
- the global log instance was moved to request instance, please see documentation for details
|
||||
- the central Clockwork class api was reworked, old api is available but deprecated
|
||||
- changed Slim middleware namespaces
|
||||
|
||||
4.1.8
|
||||
|
||||
- fixed handling of index file locking failures in file storage (reported by mahagr, thanks!)
|
||||
|
||||
4.1.7
|
||||
|
||||
- fixed a rare crash in Eloquent duplicate queries detection (reported by mstaack, thanks!)
|
||||
- fixed code-style in the Laravel config (implemented by fgilio, thanks!)
|
||||
|
||||
4.1.6
|
||||
|
||||
- added support for filtering collected requests by method to Laravel integration (options requests filtered by default) (idea by mortenscheel, thanks!)
|
||||
- added support for filtering collected requests by uri and method to vanilla integration
|
||||
- fixed handling of failed file operations on index file in file storage (reported by staabm, thanks!)
|
||||
|
||||
4.1.5
|
||||
|
||||
- fixed crash on initialization in Lumen apps using queue (reported by gramparallelo, thanks!)
|
||||
|
||||
4.1.4
|
||||
|
||||
- added support for a time property to the Request:add* apis, defaults to "current time - duration"
|
||||
- fixed crash when collecting console commands with array arguments or options in the Laravel integration (implemented by mortenscheel, thanks!)
|
||||
- fixed default storage directory being one level too deep in vanilla integration
|
||||
|
||||
4.1.3
|
||||
|
||||
- fixed file storage not unlocking index when cleanup has nothing to clean (implemented by Nacoma, thanks!)
|
||||
|
||||
4.1.2
|
||||
|
||||
- fixed interaction when making HTTP requests in feature tests when collecting tests in Laravel
|
||||
- updated to Clockwork App 4.1.1
|
||||
|
||||
4.1.1
|
||||
|
||||
- added ext-json to composer.json require section (idea by staabm, thanks!)
|
||||
- fixed Clockwork being initialized too soon in Laravel integration leading to possible crashes (reported by tminich, thanks!)
|
||||
|
||||
4.1
|
||||
|
||||
- added support for command type requests with command specific metadata (commandName, commandArguments, commandArgumentsDefaults, commandOptions, commandOptionsDefaults, commandExitCode, commandOutput)
|
||||
- added support for collecting executed artisan commands in Laravel integration
|
||||
- added support for queue-job type requests with queue-job specific metadata (jobName, jobDescription, jobStatus, jobPayload, jobQueue, jobConnection, jobOptions)
|
||||
- added support for collecting executed queue-jobs in Laravel integration (also supports Laravel Horizon)
|
||||
- added support for test type requests with test specific metadata (testName, testStatus, testStatusMessage, testAsserts)
|
||||
- added support for collecting test runs in Laravel integration using PHPunit
|
||||
- added support for disabling collection of view data when collecting rendered views (new default is to collect views without data)
|
||||
- added Twig data source using the built-in Twig profiler to collect more precise Twig profiling data
|
||||
- added support for setting parent requests on requests
|
||||
- improved collecting of database queries, cache queries, dispatched queue jobs and redis commands to also collect time
|
||||
- improved the data sources filters api to allow multiple filter types
|
||||
- improved collecting of Laravel views to use a separate data source
|
||||
- improved Eloquent data source to have an additional "early" filter applied before the query is added to query counts
|
||||
- improved Eloquent data source now passes raw stack trace as second argument to filters
|
||||
- improved Laravel data source to work when response is not provided
|
||||
- improved Laravel events data source to include Laravel namespace in the default ignored events
|
||||
- improved Laravel views data source to strip view data prefixed with __
|
||||
- improved PHP data source to not set request time for cli commands
|
||||
- improved serializer to omit data below depth limit, support debugInfo, jsonSerialize and toArray methods (partially implemented by mahagr, thanks!)
|
||||
- improved log to allow overriding serializer settings via context, no longer enabled toString by default
|
||||
- improved Request class now has pre-populated request time on creation
|
||||
- improved StackTrace helper with limit option, last method, fixed filter output keys
|
||||
- improved Lumen queue and redis feature detection
|
||||
- improved vanilla integration to allow manually sending the headers early (implemented by tminich, thanks!)
|
||||
- fixed Symfony support, added support for latest Symfony 5.x and 4.x (reported by llaville, thanks!)
|
||||
- removed dark theme for the web UI setting (now configurable in the Clockwork app itself)
|
||||
- updated to Clockwork App 4.1
|
||||
|
||||
*BREAKING*
|
||||
|
||||
- multiple new settings were added to the Laravel config file
|
||||
- DataSourceInterface::reset method was added, default empty implementation is provided in the base DataSource class
|
||||
- LaravelDataSource constructor arguments changed to reflect removing the views collecting support
|
||||
|
||||
4.0.17
|
||||
|
||||
- improved performance and memory usage when doing file storage cleanup (reported by ikkez, thanks!)
|
||||
- fixed crash after running file storage cleanup
|
||||
- fixed typo in clockwork:clean argument description
|
||||
|
||||
4.0.16
|
||||
|
||||
- fixed Laravel middleware being registered too late, causing "collect data always" setting to not work (reported by Youniteus, thanks!)
|
||||
|
||||
4.0.15
|
||||
|
||||
- fixed cleanup not working with file storage (implemented by LucidTaZ, thanks!)
|
||||
|
||||
4.0.14
|
||||
|
||||
- fixed compatibility with Laravel 5.4 and earlier when resolving authenticated user
|
||||
|
||||
4.0.13
|
||||
|
||||
- fixed stack traces processing not handling call_user_func frames properly leading to wrong traces (reported by marcus-at-localhost, thanks!)
|
||||
- fixed wrong stack traces skip namespaces defaults leading to wrong traces
|
||||
- fixed vanilla integration config file missing and no longer used settings
|
||||
|
||||
4.0.12
|
||||
|
||||
- added a simple index file locking to the file storage
|
||||
- improved handling of invalid index data in the file storage (reported by nsbucky and tkaven, thanks!)
|
||||
- fixed Laravel data source crash when running without auth service (implemented by DrBenton, thanks!)
|
||||
|
||||
4.0.11
|
||||
|
||||
- updated web UI (Clockwork App 4.0.6)
|
||||
|
||||
4.0.10
|
||||
|
||||
- fixed wrong file:line for log messages (requires enabled stack traces atm)
|
||||
|
||||
4.0.9
|
||||
|
||||
- fixed duplicate queries detection reporting all relationship queries instead of only duplicates (reported by robclancy, thanks!)
|
||||
- improved the default .gitignore for metadata storage to ignore compressed metadata as well (implemented by clugg, thanks!)
|
||||
|
||||
4.0.8
|
||||
|
||||
- updated web UI (Clockwork App 4.0.5)
|
||||
|
||||
4.0.7
|
||||
|
||||
- updated web UI (Clockwork App 4.0.4)
|
||||
|
||||
4.0.6
|
||||
|
||||
- fixed possible crash in LaravelDataSource when resolving authenticated user in non-standard auth implementations (4.0 regression) (implemented by zarunet, thanks!)
|
||||
- fixed StackTrace::filter calling array_filter with swapped arguments (implemented by villermen, thanks!)
|
||||
- fixed PHP 5.x incompatibility tenaming the Storage\Search empty and notEmpty methods to isEmpty and isNotEmpty (reported by eduardodgarciac, thanks!)
|
||||
- updated web UI (Clockwork App 4.0.3)
|
||||
|
||||
4.0.5
|
||||
|
||||
- fixed multiple issues causing FileStorage cleanup to not delete old metadata or crash (partially implemented by jaumesala, reported by SerafimArts, thanks!)
|
||||
- updated web UI (Clockwork App 4.0.2)
|
||||
|
||||
4.0.4
|
||||
|
||||
- fixed web UI not working (4.0.2 regression) (reported by williamqian and lachlankrautz, thanks!)
|
||||
|
||||
4.0.3
|
||||
|
||||
- fixed crash when using SQL storage (reported by sebastiaanluca, thanks!)
|
||||
|
||||
4.0.2
|
||||
|
||||
- updated web UI (Clockwork App 4.0.1)
|
||||
|
||||
4.0.1
|
||||
|
||||
- fixed Lumen support (reported by Owlnofeathers, thanks!)
|
||||
|
||||
4.0
|
||||
|
||||
- added "features" configuration
|
||||
- added requests search (extended storage api)
|
||||
- added collecting request body data (idea by lkloon123, thanks!)
|
||||
- added collecting of dispatched queue jobs
|
||||
- added collecting Redis commands (idea by tillkruss, thanks!)
|
||||
- added collecting of database query stats separate from queries
|
||||
- added collecting of executed middleware
|
||||
- added ability to specify slow database query threshold
|
||||
- added ability to collect only slow database queries
|
||||
- added ability to disable collecting of database queries keeping database stats
|
||||
- added ability to disable collecting of cache queries keeping cache stats
|
||||
- added duplicate (N+1) database query detection (inspired by beyondcode/laravel-query-detector, thanks!)
|
||||
- added configuration to limit number of collected frames for stack traces (defaults to 10)
|
||||
- added configuration to specify skipped vendors, namespaces and files for stack traces
|
||||
- added index file to file storage
|
||||
- added support for compression in file storage
|
||||
- added new filters api to data sources
|
||||
- improved file and sql storage to support search api
|
||||
- improved symfony storage to work with file storage changes
|
||||
- improved log api to allow passing custom stack traces in context
|
||||
- improved refactored and cleaned up Laravel service provider
|
||||
- improved Lumen integration to share more code with Laravel integration
|
||||
- improved refactored sql storage a bit
|
||||
- improved timeline api, description is now optional and defaults to event name when calling startEvent (idea by robclancy, thanks!)
|
||||
- updated web UI
|
||||
- fixed regexp in vanilla integration Clockwork REST api processing
|
||||
- removed storage filter support (replaced by features configuration)
|
||||
- BREAKING configuration format changes, please re-deploy if using customized Clockwork config
|
||||
- NOTE metadata files from previous versions will need to be manually removed on upgrade
|
||||
|
||||
3.1.4
|
||||
|
||||
- improved DBALDataSource to work with custom types (thanks villermen)
|
||||
|
||||
3.1.3
|
||||
|
||||
- updated LaravelCacheDataSource to support Laravel 5.8
|
||||
|
||||
3.1.2
|
||||
|
||||
- fixed missing use statement in vanilla integration (thanks micc83)
|
||||
|
||||
3.1.1
|
||||
|
||||
- exposed the Request::setAuthenticatedUser method on the main Clockwork class
|
||||
- fixed possible crash in LaravelDataSource when resolving authenticated user in non-standard auth implementations (thanks freshleafmedia, motia)
|
||||
|
||||
3.1
|
||||
|
||||
- added new integration for vanilla PHP (thanks martbean)
|
||||
- added support for collecting authenticated user info
|
||||
- added bunch of helper methods for adding data like database queries or events to Clockwork
|
||||
- added serializer options to the config files
|
||||
- updated web UI to match latest Chrome version
|
||||
- improved collecting of exceptions
|
||||
- improved filtered uris implementation in Laravel to no longer have any performance overhead (thanks marcusbetts)
|
||||
- improved compatibility with Laravel Telescope
|
||||
- fixed numeric keys being lost on serialization of arrays (thanks ametad)
|
||||
- fixed serialization of parent class private properties
|
||||
- fixed a possible crash when resolving stack traces (thanks mbardelmeijer)
|
||||
- deprecated Clockwork::subrequest method in favor of Clockwork::addSubrequest
|
||||
|
||||
3.0.2
|
||||
|
||||
- fixed infinite redirect if dark web theme is enabled on Laravel or Lumen <5.5 (thanks pixelskribe)
|
||||
|
||||
3.0.1
|
||||
|
||||
- improved LaravelDataSource to not collect views data if it is filtered (by default)
|
||||
|
||||
3.0
|
||||
|
||||
- updated web UI to match latest Chrome version
|
||||
- added new api for user-data (custom tabs in Clockwork app)
|
||||
- added support for authentication (thanks xiaohuilam)
|
||||
- added support for collecting stack traces for log messages, queries, etc. (thanks sisve)
|
||||
- added new api for recording subrequests (thanks L3o-pold)
|
||||
- added Symfony integration beta
|
||||
- added Xdebug profiler support
|
||||
- added collecting of full URLs for requests
|
||||
- added collecting of peak memory usage
|
||||
- added ability to use dark theme for the web UI
|
||||
- added new extend-api to data soruces for extending data when it's being sent to the application
|
||||
- improved data serialization implementation - handles recursion, unlimited depth, type metadata, clear marking for protected and private properties
|
||||
- improved data serialization with configurable defaults, limit and blackboxing of classes
|
||||
- improved handling of binary bindings in EloquentDataSource (thanks sergio91pt and coderNeos)
|
||||
- improved stack traces collection to resolve original view names
|
||||
- BREAKING improved Laravel integration to type-hint contracts instead of concrete implementations (thanks robclancy)
|
||||
- improved default configuration to not collect data for Laravel Horizon requests (thanks fgilio)
|
||||
- improved LaravelDataSource view data collecting to remove Laravel Twigbridge metadata
|
||||
- changed Laravel integration to register middleware in the boot method instead of register (thanks dionysiosarvanitis)
|
||||
- changed Laravel and Lumen integrations to use a single shared Log instance
|
||||
- fixed Clockwork HTTP API returning empty object instead of null if request was not found
|
||||
- fixed Clockwork routes not returning 404 when disabled on runtime with route cache enabled (thanks joskfg)
|
||||
- BREAKING dropped Laravel 4 support
|
||||
- BREAKING dropped PHP 5.4 support, requires PHP 5.5
|
||||
|
||||
2.2.5
|
||||
|
||||
- changed SQL storage schema URI column type from VARCHAR to TEXT (thanks sumidatx)
|
||||
- fixed possible crash in file storage cleanup if the file was already deleted (thanks bcalik)
|
||||
- fixed event handling in Eloquent data source compatibility with some 3rd party packages (thanks erikgaal)
|
||||
|
||||
2.2.4
|
||||
|
||||
- drop support for collecting Laravel controller middleware (as this can have unexpected side-effects) (thanks phh)
|
||||
|
||||
2.2.3
|
||||
|
||||
- improved Server-Timing now uses the new header format (thanks kohenkatz)
|
||||
- fixed Laravel crash when gathering middleware if the controller class doesn't exist
|
||||
|
||||
2.2.2
|
||||
|
||||
- fixed compatibility with Laravel 5.2 (thanks peppeocchi)
|
||||
|
||||
2.2.1
|
||||
|
||||
- fixed Laravel 4.x support once again (thanks bcalik)
|
||||
|
||||
2.2
|
||||
|
||||
- added support for collecting route middleware (thanks Vercoutere)
|
||||
- added support for collecting routes and middleware in newer Lumen versions
|
||||
- updated Web UI to match Clockwork Chrome 2.2
|
||||
- improved Laravel support to register most event handlers only when collecting data
|
||||
- fixed Lumen middleware not being registered automatically (thanks lucian-dragomir)
|
||||
- fixed published Lumen config not being loaded
|
||||
|
||||
2.1.1
|
||||
|
||||
- fixed Laravel 4.x support (added legacy version of the config file) (thanks bcalik)
|
||||
|
||||
2.1
|
||||
|
||||
- updated Web UI to match Clockwork Chrome 2.1
|
||||
- improved Laravel support to load the default config and use env variables in the default config
|
||||
- improved Lumen support to use the standard config subsystem instead of directly accessing env variables (thanks davoaust, SunMar)
|
||||
- improved reliability of storing metadata in some cases (by using JSON_PARTIAL_OUTPUT_ON_ERROR when supported)
|
||||
- fixed wrong mime-type for javascript assets in Web UI causing it to not work in some browsers (thanks sleavitt)
|
||||
- fixed path checking in Web UI causing it to not work on Windows (thanks Malezha)
|
||||
- fixed parameters conversion in DBALDataSource (thanks andrzejenne)
|
||||
|
||||
2.0.4
|
||||
|
||||
- improved mkdir error handling in FileStorage (thanks FBnil)
|
||||
- fixed crash in LaravelEventsDataSource when firing events with associative array as payload
|
||||
|
||||
2.0.3
|
||||
|
||||
- fixed Clockwork now working when used with Laravel route cache
|
||||
|
||||
2.0.2
|
||||
|
||||
- fixed crash on attempt to clean up file storage if the project contains Clockwork 1.x metadata
|
||||
|
||||
2.0.1
|
||||
|
||||
- fixed Web UI not working in Firefox
|
||||
|
||||
2.0
|
||||
|
||||
- added Web UI
|
||||
- added new Laravel cache data source
|
||||
- added new Laravel events data source
|
||||
- added new more robust metadata storage API
|
||||
- added automatic metadata cleanup (defaults to 1 week)
|
||||
- added better metadata serialization including class names for objects
|
||||
- added PostgreSQL compatibility for the SQL storage (thanks oldskool73)
|
||||
- added Slim 3 middleware (thanks sperrichon)
|
||||
- added PSR message data source (thanks sperrichon)
|
||||
- added Doctrine DBAL data source (thanks sperrichon)
|
||||
- changed Clockwork request ids now use dashes instead of dots (thanks Tibbelit)
|
||||
- changed Laravel and Lumen integrations to no longer log data for console commands
|
||||
- changed simplified the clock Laravel helper (thanks Jergus Lejko)
|
||||
- fixed wrong version data logged in SQL storage
|
||||
- removed PHP 5.3 support, code style changes
|
||||
- removed CodeIgniter support
|
||||
- removed ability to register additional data sources via Clockwork config
|
||||
|
||||
UPGRADING
|
||||
|
||||
- update the required Clockwork version to ^2.0 in your composer.json
|
||||
- PHP 5.3 - no longer supported, you can continue using the latest 1.x version
|
||||
- CodeIgniter - no longer supported, you can continue using the latest 1.x version
|
||||
- Slim 2 - update the imported namespace from Clockwork\Support\Slim to Clockwork\Support\Slim\Legacy
|
||||
- ability to register additional data sources via Clockwork config was removed, please call app('clockwork')->addDataSource(...) in your own service provider
|
||||
|
||||
1.14.5
|
||||
|
||||
- fixed incompatibility with Laravel 4.1 an 4.2 (introduced in 1.14.3)
|
||||
|
||||
1.14.4
|
||||
|
||||
- added support for Lumen 5.5 (thanks nebez)
|
||||
|
||||
1.14.3
|
||||
|
||||
- added support for Laravel 5.5 package auto-discovery (thanks Omranic)
|
||||
- added automatic registration of the Laravel middleware (no need to edit your Http/Kernel.php anymore, existing installations don't need to be changed)
|
||||
- updated Laravel artisan clockwork:clean command for Laravel 5.5 (thanks rosswilson252)
|
||||
- fixed crash when retrieving all requests from Sql storage (thanks pies)
|
||||
|
||||
1.14.2
|
||||
|
||||
- fixed missing imports in Doctrine data source (thanks jenssegers)
|
||||
|
||||
1.14.1
|
||||
- fixed collecting Eloquent queries when using PDO_ODBC driver for real (thanks abhimanyu003)
|
||||
|
||||
1.14
|
||||
- added support for Server-Timing headers (thanks Garbee)
|
||||
- fixed compatibility with Lumen 5.4 (thanks Dimasdanz)
|
||||
- fixed collecting Eloquent queries with bindings containing backslashes (thanks fitztrev)
|
||||
- fixed collecting Eloquent queries when using PDO_ODBC driver (thanks abhimanyu003)
|
||||
- fixed collecting Doctrine queries with array bindings (thanks RolfJanssen)
|
||||
- replaced Doctrine bindings preparation code with more complete version from laravel-doctrine
|
||||
- fixed PHP 5.3 compatibility
|
||||
|
||||
1.13.1
|
||||
- fixed compatibility with Lumen 5.4 (thanks meanevo)
|
||||
|
||||
1.13
|
||||
- added support for Laravel 5.4 (thanks KKSzymanowski)
|
||||
- improved Laravel "clock" helper function now takes multiple arguments to be logged at once (eg. `clock($foo, $bar, $baz)`)
|
||||
|
||||
1.12
|
||||
- added collecting of caller file name and line number for queries and model name (Laravel 4.2+) for ORM queries to the Eloquent data source (thanks OmarMakled and fitztrev for the idea)
|
||||
- added collecting of context, caller file name and line number to the logger (thanks crissi for the idea)
|
||||
- fixed crash in Lumen data source when running unit tests with simulated requests on Lumen
|
||||
- fixed compatibility with Laravel 4.0
|
||||
|
||||
1.11.2
|
||||
- switched to PSR-4 autoloading
|
||||
- fixed Swift data source crash when sending email with no from/to address specified (thanks marksecurelogin)
|
||||
|
||||
1.11.1
|
||||
- added support for DateTimeImmutable in Doctrine data source (thanks morfin)
|
||||
- fixed not being able to log null values via the "clock" helper function
|
||||
- fixed Laravel 4.2-dev not being properly detected as 4.2 release (thanks DemianD)
|
||||
|
||||
1.11
|
||||
- added support for Lumen 5.2 (thanks lukeed)
|
||||
- added "clock" helper function
|
||||
- fixed data sources being initialized too late (thanks morfin)
|
||||
- fixed code style in Doctrine data source
|
||||
- removed Laravel log dependency from Doctrine data source
|
||||
- NOTE laravel-doctrine provides ootb support for Clockwork, you should use this instead of included Doctrine data source with Laravel
|
||||
|
||||
1.10.1
|
||||
- fixed collecting of database queries in Laravel 5.2 (thanks sebastiandedeyne)
|
||||
|
||||
1.10
|
||||
- added Laravel 5.2 support (thanks jonphipps)
|
||||
- improved file storage to allow configuring directory permissions (thanks patrick-radius)
|
||||
- fixed interaction with PHPUnit in Lumen (thanks troyharvey)
|
||||
- removed "router dispatch" timeline event for now (due to Laravel 5.2 changes)
|
||||
|
||||
1.9
|
||||
- added Lumen support (thanks dawiyo)
|
||||
- added aliases for all Clockwork parts so they can be resolved by the IoC container in Laravel and Lumen
|
||||
- fixed Laravel framework initialisation, booting and running timeline events not being recorded properly (thanks HipsterJazzbo, sisve)
|
||||
- fixed how Laravel clockwork:clean artisan command is registered (thanks freekmurze)
|
||||
- removed Lumen framework initialisation, booting and running timeline events as they are not supported by Lumen
|
||||
|
||||
1.8.1
|
||||
- fixed SQL data storage initialization if PDO is set to throw exception on error (thanks YOzaz)
|
||||
|
||||
1.8
|
||||
- added SQL data storage implementation
|
||||
- added new config options for data storage for Laravel (please re-publish the config file)
|
||||
- fixed not being able to use the Larvel route caching when using Clockwork (thanks Garbee, kylestev, cbakker86)
|
||||
|
||||
1.7
|
||||
- added support for Laravel 5 (thanks Garbee, slovenianGooner)
|
||||
- improved support for Laravel 4.1 and 4.2, Clockwork data is now available for error responses
|
||||
- added Doctrine data source (thanks matiux)
|
||||
- fixed compatibility with some old PHP 5.3 versions (thanks hailwood)
|
||||
- updated Laravel data source to capture the context for log messages (thanks hermanzhu)
|
||||
|
||||
1.6
|
||||
- improved Eloquent data source to support multiple databases (thanks ingro)
|
||||
- improved compatibility with Laravel apps not using database
|
||||
- improved compatibility with various CodeIngiter installations
|
||||
- fixed a bug where log messages and timeline data might not be sorted correctly
|
||||
- fixed missing static keyword in CodeIgniter hook (thanks noevidenz)
|
||||
- changed Timeline::endEvent behavior to return false instead of throwing exception when called for non-existing event
|
||||
|
||||
1.5
|
||||
- improved Slim support to use DI container to share Clockwork instance instead of config
|
||||
- improved Slim support now adds all messages logged via Slim's log interface to Clockwork log as well
|
||||
- improved CodeIgniter support to make Clockwork available through the CI app (tnx BradEstey)
|
||||
- fixed Laravel support breaking flash messages (tnx hannesvdvreken)
|
||||
- fixed CodeIgniter support PSR-0 autoloading and other improvements (tnx pwhelan)
|
||||
- fixed file storage warning when recursive data is collected
|
||||
|
||||
1.4.4
|
||||
- changed Laravel support to disable permanent data collection by default (tnx jenssegers)
|
||||
- improved Laravel support to return Clockwork data with proper Content-Type (tnx maximebeaudoin)
|
||||
- fixed CodeIgniter support compatibility with PHP 5.3 (tnx BradEstey)
|
||||
|
||||
1.4.3
|
||||
- fixed incorrect requests ids being generated depending on set locale
|
||||
|
||||
1.4.2
|
||||
- fixed Laravel support compatibility with PHP 5.3
|
||||
|
||||
1.4.1
|
||||
- fixed Laravel support compatibility with PHP 5.3
|
||||
|
||||
1.4
|
||||
- added support for collecting emails and views data
|
||||
- added support for CodeIgniter 2.1 (tnx pwhelan)
|
||||
- added data source and plugin for collecting emails data from Swift mailer
|
||||
- added support for collecting emails and views data from Laravel
|
||||
- added --age argument to Laravel artisan clockwork::clean command, specifies how old the request data must be to be deleted (in hours)
|
||||
- improved Laravel service provider
|
||||
- fixed compatibility with latest Laravel 4.1
|
||||
|
||||
1.3
|
||||
NOTE: Clockwork\Request\Log::log method arguments have been changed from log($message, $level) to log($level, $message), levels are now specified via Psr\Log\LogLevel class, it's recommended to use shortcut methods for various levels (emergency, alert, critical, error, warning, notice, info and debug($message))
|
||||
- clockwork log class now implements PSR logger interface, updated Laravel and Monolog support to use all available log levels
|
||||
- clockwork log now accepts objects and arrays as input and logs their json representation
|
||||
- added support for specifying additional headers on metadata requests (Laravel) (tnx philsturgeon)
|
||||
|
||||
1.2
|
||||
- added support for Laravel 4.1
|
||||
- added facade for Laravel
|
||||
- added ability to disable collecting data about requests to specified URIs in Laravel
|
||||
- added clockwork:clean artisan command for cleaning request metadata for Laravel
|
||||
- added an easy way to add timeline events and log records via main Clockwork class
|
||||
- added support for Slim apps running in subdirs (requires Clockwork Chrome 1.1+)
|
||||
- file storage now creates default gitignore file for the request data when creating the storage dir
|
||||
- fixed a few bugs which might cause request data to not appear in Chrome extension
|
||||
- fixed a few bugs that could lead to PHP errors/exceptions
|
||||
|
||||
1.1
|
||||
- added support for Laravel 4 apps running in subdirs (requires Clockwork Chrome 1.1+)
|
||||
- added data-protocol version to the request data
|
||||
- updated Laravel 4 service provider to work with Clockwork Web
|
||||
- fixed a bug where Clockwork would break Laravel 4 apps not using database
|
||||
- fixed a bug where calling Timeline::endEvent after Timeline::finalize caused exception to be thrown
|
||||
- fixed a bug where using certain filters would store incorrect data
|
||||
|
||||
0.9.1
|
||||
- added support for application routes (ootb support for Laravel 4 only atm)
|
||||
- added configuration file for Laravel 4
|
||||
- added support for filtering stored data in Storage
|
||||
- added library version constant Clockwork::VERSION
|
13
vendor/itsgoingd/clockwork/Clockwork/Authentication/AuthenticatorInterface.php
vendored
Normal file
13
vendor/itsgoingd/clockwork/Clockwork/Authentication/AuthenticatorInterface.php
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php namespace Clockwork\Authentication;
|
||||
|
||||
interface AuthenticatorInterface
|
||||
{
|
||||
const REQUIRES_USERNAME = 'username';
|
||||
const REQUIRES_PASSWORD = 'password';
|
||||
|
||||
public function attempt(array $credentials);
|
||||
|
||||
public function check($token);
|
||||
|
||||
public function requires();
|
||||
}
|
19
vendor/itsgoingd/clockwork/Clockwork/Authentication/NullAuthenticator.php
vendored
Normal file
19
vendor/itsgoingd/clockwork/Clockwork/Authentication/NullAuthenticator.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php namespace Clockwork\Authentication;
|
||||
|
||||
class NullAuthenticator implements AuthenticatorInterface
|
||||
{
|
||||
public function attempt(array $credentials)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function check($token)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function requires()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
34
vendor/itsgoingd/clockwork/Clockwork/Authentication/SimpleAuthenticator.php
vendored
Normal file
34
vendor/itsgoingd/clockwork/Clockwork/Authentication/SimpleAuthenticator.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php namespace Clockwork\Authentication;
|
||||
|
||||
class SimpleAuthenticator implements AuthenticatorInterface
|
||||
{
|
||||
protected $password;
|
||||
|
||||
public function __construct($password)
|
||||
{
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
public function attempt(array $credentials)
|
||||
{
|
||||
if (! isset($credentials['password'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! hash_equals($credentials['password'], $this->password)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return password_hash($this->password, \PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
public function check($token)
|
||||
{
|
||||
return password_verify($this->password, $token);
|
||||
}
|
||||
|
||||
public function requires()
|
||||
{
|
||||
return [ AuthenticatorInterface::REQUIRES_PASSWORD ];
|
||||
}
|
||||
}
|
283
vendor/itsgoingd/clockwork/Clockwork/Clockwork.php
vendored
Normal file
283
vendor/itsgoingd/clockwork/Clockwork/Clockwork.php
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php namespace Clockwork;
|
||||
|
||||
use Clockwork\Authentication\AuthenticatorInterface;
|
||||
use Clockwork\Authentication\NullAuthenticator;
|
||||
use Clockwork\DataSource\DataSourceInterface;
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Request\Log;
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Request\RequestType;
|
||||
use Clockwork\Request\ShouldCollect;
|
||||
use Clockwork\Request\ShouldRecord;
|
||||
use Clockwork\Storage\StorageInterface;
|
||||
use Closure;
|
||||
|
||||
// A central class implementing the core flow of the library
|
||||
class Clockwork
|
||||
{
|
||||
// Clockwork library version
|
||||
const VERSION = '5.1.12';
|
||||
|
||||
// Array of data sources, these objects collect metadata for the current application run
|
||||
protected $dataSources = [];
|
||||
|
||||
// Request object, data structure which stores metadata about the current application run
|
||||
protected $request;
|
||||
|
||||
// Storage object, provides implementation for storing and retrieving request objects
|
||||
protected $storage;
|
||||
|
||||
// Authenticator implementation, authenticates requests for clockwork metadata
|
||||
protected $authenticator;
|
||||
|
||||
// An object specifying the rules for collecting requests
|
||||
protected $shouldCollect;
|
||||
|
||||
// An object specifying the rules for recording requests
|
||||
protected $shouldRecord;
|
||||
|
||||
// Create a new Clockwork instance with default request object, a storage implementation has to be additionally set
|
||||
public function __construct()
|
||||
{
|
||||
$this->request = new Request;
|
||||
$this->authenticator = new NullAuthenticator;
|
||||
|
||||
$this->shouldCollect = new ShouldCollect;
|
||||
$this->shouldRecord = new ShouldRecord;
|
||||
}
|
||||
|
||||
// Add a new data source
|
||||
public function addDataSource(DataSourceInterface $dataSource)
|
||||
{
|
||||
$this->dataSources[] = $dataSource;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Resolve the current request, sending it through all data sources, finalizing log and timeline
|
||||
public function resolveRequest()
|
||||
{
|
||||
foreach ($this->dataSources as $dataSource) {
|
||||
$dataSource->resolve($this->request);
|
||||
}
|
||||
|
||||
$this->request->log()->sort();
|
||||
$this->request->timeline()->finalize($this->request->time);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Resolve the current request as a "command" type request with command-specific data
|
||||
public function resolveAsCommand($name, $exitCode = null, $arguments = [], $options = [], $argumentsDefaults = [], $optionsDefaults = [], $output = null)
|
||||
{
|
||||
$this->resolveRequest();
|
||||
|
||||
$this->request->type = RequestType::COMMAND;
|
||||
$this->request->commandName = $name;
|
||||
$this->request->commandArguments = $arguments;
|
||||
$this->request->commandArgumentsDefaults = $argumentsDefaults;
|
||||
$this->request->commandOptions = $options;
|
||||
$this->request->commandOptionsDefaults = $optionsDefaults;
|
||||
$this->request->commandExitCode = $exitCode;
|
||||
$this->request->commandOutput = $output;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Resolve the current request as a "queue-job" type request with queue-job-specific data
|
||||
public function resolveAsQueueJob($name, $description = null, $status = 'processed', $payload = [], $queue = null, $connection = null, $options = [])
|
||||
{
|
||||
$this->resolveRequest();
|
||||
|
||||
$this->request->type = RequestType::QUEUE_JOB;
|
||||
$this->request->jobName = $name;
|
||||
$this->request->jobDescription = $description;
|
||||
$this->request->jobStatus = $status;
|
||||
$this->request->jobPayload = (new Serializer)->normalize($payload);
|
||||
$this->request->jobQueue = $queue;
|
||||
$this->request->jobConnection = $connection;
|
||||
$this->request->jobOptions = (new Serializer)->normalizeEach($options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Resolve the current request as a "test" type request with test-specific data, accepts test name, status, status
|
||||
// message in case of failure and array of ran asserts
|
||||
public function resolveAsTest($name, $status = 'passed', $statusMessage = null, $asserts = [])
|
||||
{
|
||||
$this->resolveRequest();
|
||||
|
||||
$this->request->type = RequestType::TEST;
|
||||
$this->request->testName = $name;
|
||||
$this->request->testStatus = $status;
|
||||
$this->request->testStatusMessage = $statusMessage;
|
||||
|
||||
foreach ($asserts as $assert) {
|
||||
$this->request->addTestAssert($assert['name'], $assert['arguments'], $assert['passed'], $assert['trace']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Extends the request with an additional data form all data sources, which is not required for normal use
|
||||
public function extendRequest(Request $request = null)
|
||||
{
|
||||
foreach ($this->dataSources as $dataSource) {
|
||||
$dataSource->extend($request ?: $this->request);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Store the current request via configured storage implementation
|
||||
public function storeRequest()
|
||||
{
|
||||
return $this->storage->store($this->request);
|
||||
}
|
||||
|
||||
// Reset all data sources to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
foreach ($this->dataSources as $dataSource) {
|
||||
$dataSource->reset();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Get or set the current request instance
|
||||
public function request(Request $request = null)
|
||||
{
|
||||
if (! $request) return $this->request;
|
||||
|
||||
$this->request = $request;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Get the log instance for the current request or log a new message
|
||||
public function log($level = null, $message = null, array $context = [])
|
||||
{
|
||||
if ($level) {
|
||||
return $this->request->log()->log($level, $message, $context);
|
||||
}
|
||||
|
||||
return $this->request->log();
|
||||
}
|
||||
|
||||
// Get the timeline instance for the current request
|
||||
public function timeline()
|
||||
{
|
||||
return $this->request->timeline();
|
||||
}
|
||||
|
||||
// Shortcut to create a new event on the current timeline instance
|
||||
public function event($description, $data = [])
|
||||
{
|
||||
return $this->request->timeline()->event($description, $data);
|
||||
}
|
||||
|
||||
// Configure which requests should be collected, can be called with arrey of options, a custom closure or with no
|
||||
// arguments for a fluent configuration api
|
||||
public function shouldCollect($shouldCollect = null)
|
||||
{
|
||||
if ($shouldCollect instanceof Closure) return $this->shouldCollect->callback($shouldCollect);
|
||||
|
||||
if (is_array($shouldCollect)) return $this->shouldCollect->merge($shouldCollect);
|
||||
|
||||
return $this->shouldCollect;
|
||||
}
|
||||
|
||||
// Configure which requests should be recorded, can be called with arrey of options, a custom closure or with no
|
||||
// arguments for a fluent configuration api
|
||||
public function shouldRecord($shouldRecord = null)
|
||||
{
|
||||
if ($shouldRecord instanceof Closure) return $this->shouldRecord->callback($shouldRecord);
|
||||
|
||||
if (is_array($shouldRecord)) return $this->shouldRecord->merge($shouldRecord);
|
||||
|
||||
return $this->shouldRecord;
|
||||
}
|
||||
|
||||
// Get or set all data sources at once
|
||||
public function dataSources($dataSources = null)
|
||||
{
|
||||
if (! $dataSources) return $this->dataSources;
|
||||
|
||||
$this->dataSources = $dataSources;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Get or set a storage implementation
|
||||
public function storage(StorageInterface $storage = null)
|
||||
{
|
||||
if (! $storage) return $this->storage;
|
||||
|
||||
$this->storage = $storage;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Get or set an authenticator implementation
|
||||
public function authenticator(AuthenticatorInterface $authenticator = null)
|
||||
{
|
||||
if (! $authenticator) return $this->authenticator;
|
||||
|
||||
$this->authenticator = $authenticator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Forward any other method calls to the current request and log instances
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (in_array($method, [ 'emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug' ])) {
|
||||
return $this->request->log()->$method(...$args);
|
||||
}
|
||||
|
||||
return $this->request->$method(...$args);
|
||||
}
|
||||
|
||||
// DEPRECATED The following apis are deprecated and will be removed in a future version
|
||||
|
||||
// Get all added data sources
|
||||
public function getDataSources()
|
||||
{
|
||||
return $this->dataSources;
|
||||
}
|
||||
|
||||
// Get the current request instance
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
// Set the current request instance
|
||||
public function setRequest(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Get a storage implementation
|
||||
public function getStorage()
|
||||
{
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
// Set a storage implementation
|
||||
public function setStorage(StorageInterface $storage)
|
||||
{
|
||||
$this->storage = $storage;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Get an authenticator implementation
|
||||
public function getAuthenticator()
|
||||
{
|
||||
return $this->authenticator;
|
||||
}
|
||||
|
||||
// Set an authenticator implementation
|
||||
public function setAuthenticator(AuthenticatorInterface $authenticator)
|
||||
{
|
||||
$this->authenticator = $authenticator;
|
||||
return $this;
|
||||
}
|
||||
}
|
64
vendor/itsgoingd/clockwork/Clockwork/DataSource/Concerns/EloquentDetectDuplicateQueries.php
vendored
Normal file
64
vendor/itsgoingd/clockwork/Clockwork/DataSource/Concerns/EloquentDetectDuplicateQueries.php
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php namespace Clockwork\DataSource\Concerns;
|
||||
|
||||
use Clockwork\Helpers\StackFilter;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
use Clockwork\Request\Log;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
// Duplicate (N+1) queries detection for EloquentDataSource, inspired by the beyondcode/laravel-query-detector package
|
||||
// by Marcel Pociot (https://github.com/beyondcode/laravel-query-detector)
|
||||
trait EloquentDetectDuplicateQueries
|
||||
{
|
||||
protected $duplicateQueries = [];
|
||||
|
||||
protected function appendDuplicateQueriesWarnings(Request $request)
|
||||
{
|
||||
$log = new Log;
|
||||
|
||||
foreach ($this->duplicateQueries as $query) {
|
||||
if ($query['count'] <= 1) continue;
|
||||
|
||||
$log->warning(
|
||||
"N+1 queries: {$query['model']}::{$query['relation']} loaded {$query['count']} times.",
|
||||
[ 'performance' => true, 'trace' => $query['trace'] ]
|
||||
);
|
||||
}
|
||||
|
||||
$request->log()->merge($log);
|
||||
}
|
||||
|
||||
protected function detectDuplicateQuery(StackTrace $trace)
|
||||
{
|
||||
$relationFrame = $trace->first(function ($frame) {
|
||||
return $frame->function == 'getRelationValue'
|
||||
|| $frame->class == \Illuminate\Database\Eloquent\Relations\Relation::class;
|
||||
});
|
||||
|
||||
if (! $relationFrame || ! $relationFrame->object) return;
|
||||
|
||||
if ($relationFrame->class == \Illuminate\Database\Eloquent\Relations\Relation::class) {
|
||||
$model = get_class($relationFrame->object->getParent());
|
||||
$relation = get_class($relationFrame->object->getRelated());
|
||||
} else {
|
||||
$model = get_class($relationFrame->object);
|
||||
$relation = $relationFrame->args[0];
|
||||
}
|
||||
|
||||
$shortTrace = $trace->skip(StackFilter::make()
|
||||
->isNotVendor([ 'itsgoingd', 'laravel', 'illuminate' ])
|
||||
->isNotNamespace([ 'Clockwork', 'Illuminate' ]));
|
||||
|
||||
$hash = implode('-', [ $model, $relation, $shortTrace->first()->file, $shortTrace->first()->line ]);
|
||||
|
||||
if (! isset($this->duplicateQueries[$hash])) {
|
||||
$this->duplicateQueries[$hash] = [
|
||||
'count' => 0,
|
||||
'model' => $model,
|
||||
'relation' => $relation,
|
||||
'trace' => $trace
|
||||
];
|
||||
}
|
||||
|
||||
$this->duplicateQueries[$hash]['count']++;
|
||||
}
|
||||
}
|
191
vendor/itsgoingd/clockwork/Clockwork/DataSource/DBALDataSource.php
vendored
Normal file
191
vendor/itsgoingd/clockwork/Clockwork/DataSource/DBALDataSource.php
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Logging\LoggerChain;
|
||||
use Doctrine\DBAL\Logging\SQLLogger;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
// Data source for DBAL, provides database queries
|
||||
class DBALDataSource extends DataSource implements SQLLogger
|
||||
{
|
||||
// Array of collected queries
|
||||
protected $queries = [];
|
||||
|
||||
// Current running query
|
||||
protected $query = null;
|
||||
|
||||
// DBAL connection
|
||||
protected $connection;
|
||||
|
||||
// DBAL connection name
|
||||
protected $connectionName;
|
||||
|
||||
// Create a new data source instance, takes a DBAL connection instance as an argument
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->connectionName = $this->connection->getDatabase();
|
||||
|
||||
$configuration = $this->connection->getConfiguration();
|
||||
$currentLogger = $configuration->getSQLLogger();
|
||||
|
||||
if ($currentLogger === null) {
|
||||
$configuration->setSQLLogger($this);
|
||||
} else {
|
||||
$loggerChain = new LoggerChain;
|
||||
$loggerChain->addLogger($currentLogger);
|
||||
$loggerChain->addLogger($this);
|
||||
|
||||
$configuration->setSQLLogger($loggerChain);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds executed database queries to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->databaseQueries = array_merge($request->databaseQueries, $this->queries);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->queries = [];
|
||||
$this->query = null;
|
||||
}
|
||||
|
||||
// DBAL SQLLogger event
|
||||
public function startQuery($sql, array $params = null, array $types = null)
|
||||
{
|
||||
$this->query = [
|
||||
'query' => $sql,
|
||||
'params' => $params,
|
||||
'types' => $types,
|
||||
'time' => microtime(true)
|
||||
];
|
||||
}
|
||||
|
||||
// DBAL SQLLogger event
|
||||
public function stopQuery()
|
||||
{
|
||||
$this->registerQuery($this->query);
|
||||
$this->query = null;
|
||||
}
|
||||
|
||||
// Collect an executed database query
|
||||
protected function registerQuery($query)
|
||||
{
|
||||
$query = [
|
||||
'query' => $this->createRunnableQuery($query['query'], $query['params'], $query['types']),
|
||||
'bindings' => $query['params'],
|
||||
'duration' => (microtime(true) - $query['time']) * 1000,
|
||||
'connection' => $this->connectionName,
|
||||
'time' => $query['time']
|
||||
];
|
||||
|
||||
if ($this->passesFilters([ $query ])) {
|
||||
$this->queries[] = $query;
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a query, an array of params and types as arguments, returns runnable query with upper-cased keywords
|
||||
protected function createRunnableQuery($query, $params, $types)
|
||||
{
|
||||
// add params to query
|
||||
$query = $this->replaceParams($this->connection->getDatabasePlatform(), $query, $params, $types);
|
||||
|
||||
// highlight keywords
|
||||
$keywords = [
|
||||
'select', 'insert', 'update', 'delete', 'into', 'values', 'set', 'where', 'from', 'limit', 'is', 'null',
|
||||
'having', 'group by', 'order by', 'asc', 'desc'
|
||||
];
|
||||
$regexp = '/\b' . implode('\b|\b', $keywords) . '\b/i';
|
||||
|
||||
return preg_replace_callback($regexp, function ($match) { return strtoupper($match[0]); }, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Source at laravel-doctrine/orm LaravelDoctrine\ORM\Loggers\Formatters\ReplaceQueryParams::format().
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @param string $sql
|
||||
* @param array|null $params
|
||||
* @param array|null $types
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function replaceParams($platform, $sql, array $params = null, array $types = null)
|
||||
{
|
||||
if (is_array($params)) {
|
||||
foreach ($params as $key => $param) {
|
||||
$type = isset($types[$key]) ? $types[$key] : null; // Originally used null coalescing
|
||||
$param = $this->convertParam($platform, $param, $type);
|
||||
$sql = preg_replace('/\?/', "$param", $sql, 1);
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source at laravel-doctrine/orm LaravelDoctrine\ORM\Loggers\Formatters\ReplaceQueryParams::convertParam().
|
||||
*
|
||||
* @param mixed $param
|
||||
*
|
||||
* @throws \Exception
|
||||
* @return string
|
||||
*/
|
||||
protected function convertParam($platform, $param, $type = null)
|
||||
{
|
||||
if (is_object($param)) {
|
||||
if (!method_exists($param, '__toString')) {
|
||||
if ($param instanceof \DateTimeInterface) {
|
||||
$param = $param->format('Y-m-d H:i:s');
|
||||
} elseif (Type::hasType($type)) {
|
||||
$type = Type::getType($type);
|
||||
$param = $type->convertToDatabaseValue($param, $platform);
|
||||
} else {
|
||||
throw new \Exception('Given query param is an instance of ' . get_class($param) . ' and could not be converted to a string');
|
||||
}
|
||||
}
|
||||
} elseif (is_array($param)) {
|
||||
if ($this->isNestedArray($param)) {
|
||||
$param = json_encode($param, JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$param = implode(
|
||||
', ',
|
||||
array_map(
|
||||
function ($part) {
|
||||
return '"' . (string) $part . '"';
|
||||
},
|
||||
$param
|
||||
)
|
||||
);
|
||||
return '(' . $param . ')';
|
||||
}
|
||||
} else {
|
||||
$param = htmlspecialchars((string) $param); // Originally used the e() Laravel helper
|
||||
}
|
||||
return '"' . (string) $param . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Source at laravel-doctrine/orm LaravelDoctrine\ORM\Loggers\Formatters\ReplaceQueryParams::isNestedArray().
|
||||
*
|
||||
* @param array $array
|
||||
* @return bool
|
||||
*/
|
||||
private function isNestedArray(array $array)
|
||||
{
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
67
vendor/itsgoingd/clockwork/Clockwork/DataSource/DataSource.php
vendored
Normal file
67
vendor/itsgoingd/clockwork/Clockwork/DataSource/DataSource.php
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
// Base data source class
|
||||
class DataSource implements DataSourceInterface
|
||||
{
|
||||
// Array of filter functions
|
||||
protected $filters = [];
|
||||
|
||||
// Adds collected data to the request and returns it, to be implemented by extending classes
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Extends the request with an additional data, which is not required for normal use
|
||||
public function extend(Request $request)
|
||||
{
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
}
|
||||
|
||||
// Register a new filter
|
||||
public function addFilter(\Closure $filter, $type = 'default')
|
||||
{
|
||||
$this->filters[$type] = isset($this->filters[$type])
|
||||
? array_merge($this->filters[$type], [ $filter ]) : [ $filter ];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Clear all registered filters
|
||||
public function clearFilters()
|
||||
{
|
||||
$this->filters = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Returns boolean whether the filterable passes all registered filters
|
||||
protected function passesFilters($args, $type = 'default')
|
||||
{
|
||||
$filters = isset($this->filters[$type]) ? $this->filters[$type] : [];
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
if (! $filter(...$args)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Censors passwords in an array, identified by key containing "pass" substring
|
||||
public function removePasswords(array $data)
|
||||
{
|
||||
$keys = array_keys($data);
|
||||
$values = array_map(function ($value, $key) {
|
||||
return strpos($key, 'pass') !== false ? '*removed*' : $value;
|
||||
}, $data, $keys);
|
||||
|
||||
return array_combine($keys, $values);
|
||||
}
|
||||
}
|
16
vendor/itsgoingd/clockwork/Clockwork/DataSource/DataSourceInterface.php
vendored
Normal file
16
vendor/itsgoingd/clockwork/Clockwork/DataSource/DataSourceInterface.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
// Data source interface, all data sources must implement this interface
|
||||
interface DataSourceInterface
|
||||
{
|
||||
// Adds collected data to the request and returns it
|
||||
public function resolve(Request $request);
|
||||
|
||||
// Extends the request with an additional data, which is not required for normal use
|
||||
public function extend(Request $request);
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset();
|
||||
}
|
12
vendor/itsgoingd/clockwork/Clockwork/DataSource/DoctrineDataSource.php
vendored
Normal file
12
vendor/itsgoingd/clockwork/Clockwork/DataSource/DoctrineDataSource.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
// Data source for Doctrine, provides database queries
|
||||
class DoctrineDataSource extends DBALDataSource
|
||||
{
|
||||
public function __construct(EntityManager $enm)
|
||||
{
|
||||
parent::__construct($enm->getConnection());
|
||||
}
|
||||
}
|
329
vendor/itsgoingd/clockwork/Clockwork/DataSource/EloquentDataSource.php
vendored
Normal file
329
vendor/itsgoingd/clockwork/Clockwork/DataSource/EloquentDataSource.php
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Support\Laravel\Eloquent\ResolveModelLegacyScope;
|
||||
use Clockwork\Support\Laravel\Eloquent\ResolveModelScope;
|
||||
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
|
||||
|
||||
// Data source for Eloquent (Laravel ORM), provides database queries, stats, model actions and counts
|
||||
class EloquentDataSource extends DataSource
|
||||
{
|
||||
use Concerns\EloquentDetectDuplicateQueries;
|
||||
|
||||
// Database manager instance
|
||||
protected $databaseManager;
|
||||
|
||||
// Event dispatcher instance
|
||||
protected $eventDispatcher;
|
||||
|
||||
// Array of collected queries
|
||||
protected $queries = [];
|
||||
|
||||
// Query counts by type
|
||||
protected $count = [
|
||||
'total' => 0, 'slow' => 0, 'select' => 0, 'insert' => 0, 'update' => 0, 'delete' => 0, 'other' => 0
|
||||
];
|
||||
|
||||
// Collected models actions
|
||||
protected $modelsActions = [];
|
||||
|
||||
// Model action counts by model, eg. [ 'retrieved' => [ User::class => 1 ] ]
|
||||
protected $modelsCount = [
|
||||
'retrieved' => [], 'created' => [], 'updated' => [], 'deleted' => []
|
||||
];
|
||||
|
||||
// Whether we are collecting database queries or stats only
|
||||
protected $collectQueries = true;
|
||||
|
||||
// Whether we are collecting models actions or stats only
|
||||
protected $collectModelsActions = true;
|
||||
|
||||
// Whether we are collecting retrieved models as well when collecting models actions
|
||||
protected $collectModelsRetrieved = false;
|
||||
|
||||
// Query execution time threshold in ms after which the query is marked as slow
|
||||
protected $slowThreshold;
|
||||
|
||||
// Enable duplicate queries detection
|
||||
protected $detectDuplicateQueries = false;
|
||||
|
||||
// Model name to associate with the next executed query, used to map queries to models
|
||||
public $nextQueryModel;
|
||||
|
||||
// Create a new data source instance, takes a database manager, an event dispatcher as arguments and additional
|
||||
// options as arguments
|
||||
public function __construct(ConnectionResolverInterface $databaseManager, EventDispatcher $eventDispatcher, $collectQueries = true, $slowThreshold = null, $slowOnly = false, $detectDuplicateQueries = false, $collectModelsActions = true, $collectModelsRetrieved = false)
|
||||
{
|
||||
$this->databaseManager = $databaseManager;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
|
||||
$this->collectQueries = $collectQueries;
|
||||
$this->slowThreshold = $slowThreshold;
|
||||
$this->detectDuplicateQueries = $detectDuplicateQueries;
|
||||
$this->collectModelsActions = $collectModelsActions;
|
||||
$this->collectModelsRetrieved = $collectModelsRetrieved;
|
||||
|
||||
if ($slowOnly) $this->addFilter(function ($query) { return $query['duration'] > $this->slowThreshold; });
|
||||
}
|
||||
|
||||
// Adds ran database queries, query counts, models actions and models counts to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->databaseQueries = array_merge($request->databaseQueries, $this->queries);
|
||||
|
||||
$request->databaseQueriesCount += $this->count['total'];
|
||||
$request->databaseSlowQueries += $this->count['slow'];
|
||||
$request->databaseSelects += $this->count['select'];
|
||||
$request->databaseInserts += $this->count['insert'];
|
||||
$request->databaseUpdates += $this->count['update'];
|
||||
$request->databaseDeletes += $this->count['delete'];
|
||||
$request->databaseOthers += $this->count['other'];
|
||||
|
||||
$request->modelsActions = array_merge($request->modelsActions, $this->modelsActions);
|
||||
|
||||
$request->modelsRetrieved = $this->modelsCount['retrieved'];
|
||||
$request->modelsCreated = $this->modelsCount['created'];
|
||||
$request->modelsUpdated = $this->modelsCount['updated'];
|
||||
$request->modelsDeleted = $this->modelsCount['deleted'];
|
||||
|
||||
$this->appendDuplicateQueriesWarnings($request);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->queries = [];
|
||||
$this->count = [
|
||||
'total' => 0, 'slow' => 0, 'select' => 0, 'insert' => 0, 'update' => 0, 'delete' => 0, 'other' => 0
|
||||
];
|
||||
|
||||
$this->modelsActions = [];
|
||||
$this->modelsCount = [
|
||||
'retrieved' => [], 'created' => [], 'updated' => [], 'deleted' => []
|
||||
];
|
||||
|
||||
$this->nextQueryModel = null;
|
||||
}
|
||||
|
||||
// Start listening to Eloquent events
|
||||
public function listenToEvents()
|
||||
{
|
||||
if ($scope = $this->getModelResolvingScope()) {
|
||||
$this->eventDispatcher->listen('eloquent.booted: *', function ($model, $data = null) use ($scope) {
|
||||
if (is_string($model) && is_array($data)) { // Laravel 5.4 wildcard event
|
||||
$model = reset($data);
|
||||
}
|
||||
|
||||
$model->addGlobalScope($scope);
|
||||
});
|
||||
}
|
||||
|
||||
if (class_exists(\Illuminate\Database\Events\QueryExecuted::class)) {
|
||||
// Laravel 5.2 and up
|
||||
$this->eventDispatcher->listen(\Illuminate\Database\Events\QueryExecuted::class, function ($event) {
|
||||
$this->registerQuery($event);
|
||||
});
|
||||
} else {
|
||||
// Laravel 5.0 to 5.1
|
||||
$this->eventDispatcher->listen('illuminate.query', function ($event) {
|
||||
$this->registerLegacyQuery($event);
|
||||
});
|
||||
}
|
||||
|
||||
// register all event listeners individually so we don't have to regex the event type and support Laravel <5.4
|
||||
$this->listenToModelEvent('retrieved');
|
||||
$this->listenToModelEvent('created');
|
||||
$this->listenToModelEvent('updated');
|
||||
$this->listenToModelEvent('deleted');
|
||||
}
|
||||
|
||||
// Register a listener collecting model events of specified type
|
||||
protected function listenToModelEvent($event)
|
||||
{
|
||||
$this->eventDispatcher->listen("eloquent.{$event}: *", function ($model, $data = null) use ($event) {
|
||||
if (is_string($model) && is_array($data)) { // Laravel 5.4 wildcard event
|
||||
$model = reset($data);
|
||||
}
|
||||
|
||||
$this->collectModelEvent($event, $model);
|
||||
});
|
||||
}
|
||||
|
||||
// Collect an executed database query
|
||||
protected function registerQuery($event)
|
||||
{
|
||||
$trace = StackTrace::get([ 'arguments' => $this->detectDuplicateQueries ])->resolveViewName();
|
||||
|
||||
if ($this->detectDuplicateQueries) $this->detectDuplicateQuery($trace);
|
||||
|
||||
$query = [
|
||||
'query' => $this->createRunnableQuery($event->sql, $event->bindings, $event->connectionName),
|
||||
'duration' => $event->time,
|
||||
'connection' => $event->connectionName,
|
||||
'time' => microtime(true) - $event->time / 1000,
|
||||
'trace' => (new Serializer)->trace($trace),
|
||||
'model' => $this->nextQueryModel,
|
||||
'tags' => $this->slowThreshold !== null && $event->time > $this->slowThreshold ? [ 'slow' ] : []
|
||||
];
|
||||
|
||||
$this->nextQueryModel = null;
|
||||
|
||||
if (! $this->passesFilters([ $query, $trace ], 'early')) return;
|
||||
|
||||
$this->incrementQueryCount($query);
|
||||
|
||||
if (! $this->collectQueries || ! $this->passesFilters([ $query, $trace ])) return;
|
||||
|
||||
$this->queries[] = $query;
|
||||
}
|
||||
|
||||
// Collect an executed database query (pre Laravel 5.2)
|
||||
protected function registerLegacyQuery($sql, $bindings, $time, $connection)
|
||||
{
|
||||
return $this->registerQuery((object) [
|
||||
'sql' => $sql,
|
||||
'bindings' => $bindings,
|
||||
'time' => $time,
|
||||
'connectionName' => $connection
|
||||
]);
|
||||
}
|
||||
|
||||
// Collect a model event and update stats
|
||||
protected function collectModelEvent($event, $model)
|
||||
{
|
||||
$lastQuery = ($queryCount = count($this->queries)) ? $this->queries[$queryCount - 1] : null;
|
||||
|
||||
$action = [
|
||||
'model' => $modelClass = get_class($model),
|
||||
'key' => $this->getModelKey($model),
|
||||
'action' => $event,
|
||||
'attributes' => $this->collectModelsRetrieved && $event == 'retrieved' ? $model->getOriginal() : [],
|
||||
'changes' => $this->collectModelsActions ? $model->getChanges() : [],
|
||||
'time' => microtime(true) / 1000,
|
||||
'query' => $lastQuery ? $lastQuery['query'] : null,
|
||||
'duration' => $lastQuery ? $lastQuery['duration'] : null,
|
||||
'connection' => $lastQuery ? $lastQuery['connection'] : null,
|
||||
'trace' => null,
|
||||
'tags' => []
|
||||
];
|
||||
|
||||
if ($lastQuery) $this->queries[$queryCount - 1]['model'] = $modelClass;
|
||||
|
||||
if (! $this->passesFilters([ $action ], 'models-early')) return;
|
||||
|
||||
$this->incrementModelsCount($action['action'], $action['model']);
|
||||
|
||||
if (! $this->collectModelsActions) return;
|
||||
if (! $this->collectModelsRetrieved && $event == 'retrieved') return;
|
||||
if (! $this->passesFilters([ $action ], 'models')) return;
|
||||
|
||||
$action['trace'] = (new Serializer)->trace(StackTrace::get()->resolveViewName());
|
||||
|
||||
$this->modelsActions[] = $action;
|
||||
}
|
||||
|
||||
// Takes a query, an array of bindings and the connection as arguments, returns runnable query with upper-cased keywords
|
||||
protected function createRunnableQuery($query, $bindings, $connection)
|
||||
{
|
||||
// add bindings to query
|
||||
$bindings = $this->databaseManager->connection($connection)->prepareBindings($bindings);
|
||||
|
||||
$index = 0;
|
||||
$query = preg_replace_callback('/\?/', function ($matches) use ($bindings, $connection, &$index) {
|
||||
$binding = $this->quoteBinding($bindings[$index++], $connection);
|
||||
|
||||
// convert binary bindings to hexadecimal representation
|
||||
if (! preg_match('//u', (string) $binding)) $binding = '0x' . bin2hex($binding);
|
||||
|
||||
// escape backslashes in the binding (preg_replace requires to do so)
|
||||
return (string) $binding;
|
||||
}, $query, count($bindings));
|
||||
|
||||
// highlight keywords
|
||||
$keywords = [
|
||||
'select', 'insert', 'update', 'delete', 'into', 'values', 'set', 'where', 'from', 'limit', 'is', 'null',
|
||||
'having', 'group by', 'order by', 'asc', 'desc'
|
||||
];
|
||||
$regexp = '/\b' . implode('\b|\b', $keywords) . '\b/i';
|
||||
|
||||
return preg_replace_callback($regexp, function ($match) { return strtoupper($match[0]); }, $query);
|
||||
}
|
||||
|
||||
// Takes a query binding and a connection name, returns a quoted binding value
|
||||
protected function quoteBinding($binding, $connection)
|
||||
{
|
||||
$connection = $this->databaseManager->connection($connection);
|
||||
|
||||
if (! method_exists($connection, 'getPdo')) return;
|
||||
|
||||
$pdo = $connection->getPdo();
|
||||
|
||||
if ($pdo === null) return;
|
||||
|
||||
if ($pdo->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'odbc') {
|
||||
// PDO_ODBC driver doesn't support the quote method, apply simple MSSQL style quoting instead
|
||||
return "'" . str_replace("'", "''", $binding) . "'";
|
||||
}
|
||||
|
||||
return is_string($binding) ? $pdo->quote($binding) : $binding;
|
||||
}
|
||||
|
||||
// Increment query counts for collected query
|
||||
protected function incrementQueryCount($query)
|
||||
{
|
||||
$sql = ltrim($query['query']);
|
||||
|
||||
$this->count['total']++;
|
||||
|
||||
if (preg_match('/^select\b/i', $sql)) {
|
||||
$this->count['select']++;
|
||||
} elseif (preg_match('/^insert\b/i', $sql)) {
|
||||
$this->count['insert']++;
|
||||
} elseif (preg_match('/^update\b/i', $sql)) {
|
||||
$this->count['update']++;
|
||||
} elseif (preg_match('/^delete\b/i', $sql)) {
|
||||
$this->count['delete']++;
|
||||
} else {
|
||||
$this->count['other']++;
|
||||
}
|
||||
|
||||
if (in_array('slow', $query['tags'])) {
|
||||
$this->count['slow']++;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment model counts for collected model action
|
||||
protected function incrementModelsCount($action, $model)
|
||||
{
|
||||
if (! isset($this->modelsCount[$action][$model])) {
|
||||
$this->modelsCount[$action][$model] = 0;
|
||||
}
|
||||
|
||||
$this->modelsCount[$action][$model]++;
|
||||
}
|
||||
|
||||
// Returns model resolving scope for the installed Laravel version
|
||||
protected function getModelResolvingScope()
|
||||
{
|
||||
if (interface_exists(\Illuminate\Database\Eloquent\ScopeInterface::class)) {
|
||||
// Laravel 5.0 to 5.1
|
||||
return new ResolveModelLegacyScope($this);
|
||||
}
|
||||
|
||||
return new ResolveModelScope($this);
|
||||
}
|
||||
|
||||
// Returns model key without crashing when using Eloquent strict mode and it's not loaded
|
||||
protected function getModelKey($model)
|
||||
{
|
||||
try {
|
||||
return $model->getKey();
|
||||
} catch (\Illuminate\Database\Eloquent\MissingAttributeException $e) {}
|
||||
}
|
||||
}
|
138
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelCacheDataSource.php
vendored
Normal file
138
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelCacheDataSource.php
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
|
||||
|
||||
// Data source for Laravel cache component, provides cache queries and stats
|
||||
class LaravelCacheDataSource extends DataSource
|
||||
{
|
||||
// Event dispatcher instance
|
||||
protected $eventDispatcher;
|
||||
|
||||
// Executed cache queries
|
||||
protected $queries = [];
|
||||
|
||||
// Query counts by type
|
||||
protected $count = [
|
||||
'read' => 0, 'hit' => 0, 'write' => 0, 'delete' => 0
|
||||
];
|
||||
|
||||
// Whether we are collecting cache queries or stats only
|
||||
protected $collectQueries = true;
|
||||
|
||||
// Whether we are collecting values from cache queries
|
||||
protected $collectValues = true;
|
||||
|
||||
// Create a new data source instance, takes an event dispatcher and additional options as argument
|
||||
public function __construct(EventDispatcher $eventDispatcher, $collectQueries = true, $collectValues = true)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
|
||||
$this->collectQueries = $collectQueries;
|
||||
$this->collectValues = $collectValues;
|
||||
}
|
||||
|
||||
// Adds cache queries and stats to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->cacheQueries = array_merge($request->cacheQueries, $this->queries);
|
||||
|
||||
$request->cacheReads += $this->count['read'];
|
||||
$request->cacheHits += $this->count['hit'];
|
||||
$request->cacheWrites += $this->count['write'];
|
||||
$request->cacheDeletes += $this->count['delete'];
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->queries = [];
|
||||
|
||||
$this->count = [
|
||||
'read' => 0, 'hit' => 0, 'write' => 0, 'delete' => 0
|
||||
];
|
||||
}
|
||||
|
||||
// Start listening to cache events
|
||||
public function listenToEvents()
|
||||
{
|
||||
if (class_exists(\Illuminate\Cache\Events\CacheHit::class)) {
|
||||
$this->eventDispatcher->listen(\Illuminate\Cache\Events\CacheHit::class, function ($event) {
|
||||
$this->registerQuery([ 'type' => 'hit', 'key' => $event->key, 'value' => $event->value ]);
|
||||
});
|
||||
$this->eventDispatcher->listen(\Illuminate\Cache\Events\CacheMissed::class, function ($event) {
|
||||
$this->registerQuery([ 'type' => 'miss', 'key' => $event->key ]);
|
||||
});
|
||||
$this->eventDispatcher->listen(\Illuminate\Cache\Events\KeyWritten::class, function ($event) {
|
||||
$this->registerQuery([
|
||||
'type' => 'write', 'key' => $event->key, 'value' => $event->value,
|
||||
'expiration' => property_exists($event, 'seconds') ? $event->seconds : $event->minutes * 60
|
||||
]);
|
||||
});
|
||||
$this->eventDispatcher->listen(\Illuminate\Cache\Events\KeyForgotten::class, function ($event) {
|
||||
$this->registerQuery([ 'type' => 'delete', 'key' => $event->key ]);
|
||||
});
|
||||
} else {
|
||||
// legacy Laravel 5.1 style events
|
||||
$this->eventDispatcher->listen('cache.hit', function ($key, $value) {
|
||||
$this->registerQuery([ 'type' => 'hit', 'key' => $key, 'value' => $value ]);
|
||||
});
|
||||
$this->eventDispatcher->listen('cache.missed', function ($key) {
|
||||
$this->registerQuery([ 'type' => 'miss', 'key' => $key ]);
|
||||
});
|
||||
$this->eventDispatcher->listen('cache.write', function ($key, $value, $minutes) {
|
||||
$this->registerQuery([
|
||||
'type' => 'write', 'key' => $key, 'value' => $value, 'expiration' => $minutes * 60
|
||||
]);
|
||||
});
|
||||
$this->eventDispatcher->listen('cache.delete', function ($key) {
|
||||
$this->registerQuery([ 'type' => 'delete', 'key' => $key ]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Collect an executed query
|
||||
protected function registerQuery(array $query)
|
||||
{
|
||||
$trace = StackTrace::get()->resolveViewName();
|
||||
|
||||
$record = [
|
||||
'type' => $query['type'],
|
||||
'key' => $query['key'],
|
||||
'time' => microtime(true),
|
||||
'connection' => null,
|
||||
'trace' => (new Serializer)->trace($trace)
|
||||
];
|
||||
|
||||
if ($this->collectValues && isset($query['value'])) {
|
||||
$record['value'] = (new Serializer)->normalize($query['value']);
|
||||
}
|
||||
|
||||
$this->incrementQueryCount($record);
|
||||
|
||||
if ($this->collectQueries && $this->passesFilters([ $record ])) {
|
||||
$this->queries[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment query counts for collected query
|
||||
protected function incrementQueryCount($query)
|
||||
{
|
||||
if ($query['type'] == 'write') {
|
||||
$this->count['write']++;
|
||||
} elseif ($query['type'] == 'delete') {
|
||||
$this->count['delete']++;
|
||||
} else {
|
||||
$this->count['read']++;
|
||||
|
||||
if ($query['type'] == 'hit') {
|
||||
$this->count['hit']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
223
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelDataSource.php
vendored
Normal file
223
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelDataSource.php
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\DataSource\DataSource;
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Request\Log;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
// Data source for Laravel framework, provides application log, request and response information
|
||||
class LaravelDataSource extends DataSource
|
||||
{
|
||||
// Laravel application instance
|
||||
protected $app;
|
||||
|
||||
// Laravel response instance
|
||||
protected $response;
|
||||
|
||||
// Whether we should collect log messages
|
||||
protected $collectLog = true;
|
||||
|
||||
// Whether we should collect routes
|
||||
protected $collectRoutes = false;
|
||||
|
||||
// Only collect routes from following list of namespaces (collect all if empty)
|
||||
protected $routesOnlyNamespaces = [];
|
||||
|
||||
// Clockwork log instance
|
||||
protected $log;
|
||||
|
||||
// Create a new data source, takes Laravel application instance and additional options as an arguments
|
||||
public function __construct(Application $app, $collectLog = true, $collectRoutes = false, $routesOnlyNamespaces = true)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
||||
$this->collectLog = $collectLog;
|
||||
$this->collectRoutes = $collectRoutes;
|
||||
$this->routesOnlyNamespaces = $routesOnlyNamespaces;
|
||||
|
||||
$this->log = new Log;
|
||||
}
|
||||
|
||||
// Adds request, response information, middleware, routes, session data, user and log entries to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->method = $this->getRequestMethod();
|
||||
$request->url = $this->getRequestUrl();
|
||||
$request->uri = $this->getRequestUri();
|
||||
$request->controller = $this->getController();
|
||||
$request->headers = $this->getRequestHeaders();
|
||||
$request->responseStatus = $this->getResponseStatus();
|
||||
$request->middleware = $this->getMiddleware();
|
||||
$request->routes = $this->getRoutes();
|
||||
$request->sessionData = $this->getSessionData();
|
||||
|
||||
$this->resolveAuthenticatedUser($request);
|
||||
|
||||
$request->log()->merge($this->log);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->log = new Log;
|
||||
}
|
||||
|
||||
// Set Laravel application instance for the current request
|
||||
public function setApplication(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Set Laravel response instance for the current request
|
||||
public function setResponse(Response $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Listen for the log events
|
||||
public function listenToEvents()
|
||||
{
|
||||
if (! $this->collectLog) return;
|
||||
|
||||
if (class_exists(\Illuminate\Log\Events\MessageLogged::class)) {
|
||||
// Laravel 5.4
|
||||
$this->app['events']->listen(\Illuminate\Log\Events\MessageLogged::class, function ($event) {
|
||||
$this->log->log($event->level, $event->message, $event->context);
|
||||
});
|
||||
} else {
|
||||
// Laravel 5.0 to 5.3
|
||||
$this->app['events']->listen('illuminate.log', function ($level, $message, $context) {
|
||||
$this->log->log($level, $message, $context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get a textual representation of the current route's controller
|
||||
protected function getController()
|
||||
{
|
||||
$router = $this->app['router'];
|
||||
|
||||
$route = $router->current();
|
||||
$controller = $route ? $route->getActionName() : null;
|
||||
|
||||
if ($controller instanceof \Closure) {
|
||||
$controller = 'anonymous function';
|
||||
} elseif (is_object($controller)) {
|
||||
$controller = 'instance of ' . get_class($controller);
|
||||
} elseif (is_array($controller) && count($controller) == 2) {
|
||||
if (is_object($controller[0])) {
|
||||
$controller = get_class($controller[0]) . '->' . $controller[1];
|
||||
} else {
|
||||
$controller = $controller[0] . '::' . $controller[1];
|
||||
}
|
||||
} elseif (! is_string($controller)) {
|
||||
$controller = null;
|
||||
}
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
// Get the request headers
|
||||
protected function getRequestHeaders()
|
||||
{
|
||||
return $this->app['request']->headers->all();
|
||||
}
|
||||
|
||||
// Get the request method
|
||||
protected function getRequestMethod()
|
||||
{
|
||||
return $this->app['request']->getMethod();
|
||||
}
|
||||
|
||||
// Get the request URL
|
||||
protected function getRequestUrl()
|
||||
{
|
||||
return $this->app['request']->fullUrl();
|
||||
}
|
||||
|
||||
// Get the request URI
|
||||
protected function getRequestUri()
|
||||
{
|
||||
return $this->app['request']->getRequestUri();
|
||||
}
|
||||
|
||||
// Get the response status code
|
||||
protected function getResponseStatus()
|
||||
{
|
||||
return $this->response ? $this->response->getStatusCode() : null;
|
||||
}
|
||||
|
||||
// Get an array of middleware for the matched route
|
||||
protected function getMiddleware()
|
||||
{
|
||||
$route = $this->app['router']->current();
|
||||
|
||||
if (! $route) return;
|
||||
|
||||
return method_exists($route, 'gatherMiddleware') ? $route->gatherMiddleware() : $route->middleware();
|
||||
}
|
||||
|
||||
// Get an array of application routes
|
||||
protected function getRoutes()
|
||||
{
|
||||
if (! $this->collectRoutes) return [];
|
||||
|
||||
return array_values(array_filter(array_map(function ($route) {
|
||||
$action = $route->getActionName() ?: 'anonymous function';
|
||||
$namespace = strpos($action, '\\') !== false ? explode('\\', $action)[0] : null;
|
||||
|
||||
if (count($this->routesOnlyNamespaces) && ! in_array($namespace, $this->routesOnlyNamespaces)) return;
|
||||
|
||||
return [
|
||||
'method' => implode(', ', $route->methods()),
|
||||
'uri' => $route->uri(),
|
||||
'name' => $route->getName(),
|
||||
'action' => $action,
|
||||
'middleware' => $route->middleware(),
|
||||
'before' => method_exists($route, 'beforeFilters') ? implode(', ', array_keys($route->beforeFilters())) : '',
|
||||
'after' => method_exists($route, 'afterFilters') ? implode(', ', array_keys($route->afterFilters())) : ''
|
||||
];
|
||||
}, $this->app['router']->getRoutes()->getRoutes())));
|
||||
}
|
||||
|
||||
// Get the session data (normalized with removed passwords)
|
||||
protected function getSessionData()
|
||||
{
|
||||
if (! isset($this->app['session'])) return [];
|
||||
|
||||
return $this->removePasswords((new Serializer)->normalizeEach($this->app['session']->all()));
|
||||
}
|
||||
|
||||
// Add authenticated user data to the request
|
||||
protected function resolveAuthenticatedUser(Request $request)
|
||||
{
|
||||
if (! isset($this->app['auth'])) return;
|
||||
if (! ($user = $this->app['auth']->user())) return;
|
||||
|
||||
if ($user instanceof \Illuminate\Database\Eloquent\Model) {
|
||||
// retrieve attributes in this awkward way to make sure we don't trigger exceptions with Eloquent strict mode on
|
||||
$keyName = method_exists($user, 'getAuthIdentifierName') ? $user->getAuthIdentifierName() : $user->getKeyName();
|
||||
$user = $user->getAttributes();
|
||||
|
||||
$userId = isset($user[$keyName]) ? $user[$keyName] : null;
|
||||
$userEmail = isset($user['email']) ? $user['email'] : $userId;
|
||||
$userName = isset($user['name']) ? $user['name'] : null;
|
||||
} else {
|
||||
$userId = $user->getAuthIdentifier();
|
||||
$userEmail = isset($user->email) ? $user->email : $userId;
|
||||
$userName = isset($user->name) ? $user->name : null;
|
||||
}
|
||||
|
||||
$request->setAuthenticatedUser($userEmail, $userId, [
|
||||
'email' => $userEmail,
|
||||
'name' => $userName
|
||||
]);
|
||||
}
|
||||
}
|
143
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelEventsDataSource.php
vendored
Normal file
143
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelEventsDataSource.php
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
// Data source for Laravel events component, provides fired events
|
||||
class LaravelEventsDataSource extends DataSource
|
||||
{
|
||||
// Event dispatcher instance
|
||||
protected $dispatcher;
|
||||
|
||||
// Fired events
|
||||
protected $events = [];
|
||||
|
||||
// Whether framework events should be collected
|
||||
protected $ignoredEvents = false;
|
||||
|
||||
// Create a new data source instance, takes an event dispatcher and additional options as arguments
|
||||
public function __construct(Dispatcher $dispatcher, $ignoredEvents = [])
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
|
||||
$this->ignoredEvents = is_array($ignoredEvents)
|
||||
? array_merge($ignoredEvents, $this->defaultIgnoredEvents()) : [];
|
||||
}
|
||||
|
||||
// Adds fired events to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->events = array_merge($request->events, $this->events);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->events = [];
|
||||
}
|
||||
|
||||
// Start listening to the events
|
||||
public function listenToEvents()
|
||||
{
|
||||
$this->dispatcher->listen('*', function ($event = null, $data = null) {
|
||||
if (method_exists($this->dispatcher, 'firing')) { // Laravel 5.0 - 5.3
|
||||
$data = func_get_args();
|
||||
$event = $this->dispatcher->firing();
|
||||
}
|
||||
|
||||
$this->registerEvent($event, $data);
|
||||
});
|
||||
}
|
||||
|
||||
// Collect a fired event, prepares data for serialization and resolves registered listeners
|
||||
protected function registerEvent($event, array $data)
|
||||
{
|
||||
if (! $this->shouldCollect($event)) return;
|
||||
|
||||
$trace = StackTrace::get()->resolveViewName();
|
||||
|
||||
$event = [
|
||||
'event' => $event,
|
||||
'data' => (new Serializer)->normalize(count($data) == 1 && isset($data[0]) ? $data[0] : $data),
|
||||
'time' => microtime(true),
|
||||
'listeners' => $this->findListenersFor($event),
|
||||
'trace' => (new Serializer)->trace($trace)
|
||||
];
|
||||
|
||||
if ($this->passesFilters([ $event ])) {
|
||||
$this->events[] = $event;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns registered listeners for the specified event
|
||||
protected function findListenersFor($event)
|
||||
{
|
||||
$listener = $this->dispatcher->getListeners($event)[0];
|
||||
|
||||
return array_filter(array_map(function ($listener) {
|
||||
if ($listener instanceof \Closure) {
|
||||
// Laravel 5.4+ (and earlier versions in some cases) wrap the listener into a closure,
|
||||
// attempt to resolve the original listener
|
||||
$use = (new \ReflectionFunction($listener))->getStaticVariables();
|
||||
$listener = isset($use['listener']) ? $use['listener'] : $listener;
|
||||
}
|
||||
|
||||
if (is_string($listener)) {
|
||||
return $listener;
|
||||
} elseif (is_array($listener) && count($listener) == 2) {
|
||||
if (is_object($listener[0])) {
|
||||
return get_class($listener[0]) . '@' . $listener[1];
|
||||
} else {
|
||||
return $listener[0] . '::' . $listener[1];
|
||||
}
|
||||
} elseif ($listener instanceof \Closure) {
|
||||
$listener = new \ReflectionFunction($listener);
|
||||
|
||||
if (strpos($listener->getNamespaceName(), 'Clockwork\\') === 0) { // skip our own listeners
|
||||
return;
|
||||
}
|
||||
|
||||
$filename = str_replace(base_path(), '', $listener->getFileName());
|
||||
$startLine = $listener->getStartLine();
|
||||
$endLine = $listener->getEndLine();
|
||||
|
||||
return "Closure ({$filename}:{$startLine}-{$endLine})";
|
||||
}
|
||||
}, $this->dispatcher->getListeners($event)));
|
||||
}
|
||||
|
||||
// Returns whether the event should be collected (depending on ignored events)
|
||||
protected function shouldCollect($event)
|
||||
{
|
||||
return ! preg_match('/^(?:' . implode('|', $this->ignoredEvents) . ')$/', $event);
|
||||
}
|
||||
|
||||
// Returns default ignored events (framework-specific events)
|
||||
protected function defaultIgnoredEvents()
|
||||
{
|
||||
return [
|
||||
'Illuminate\\\\.+',
|
||||
'Laravel\\\\.+',
|
||||
'auth\.(?:attempt|login|logout)',
|
||||
'artisan\.start',
|
||||
'bootstrapped:.+',
|
||||
'composing:.+',
|
||||
'creating:.+',
|
||||
'illuminate\.query',
|
||||
'connection\..+',
|
||||
'eloquent\..+',
|
||||
'kernel\.handled',
|
||||
'illuminate\.log',
|
||||
'mailer\.sending',
|
||||
'router\.(?:before|after|matched)',
|
||||
'router.filter:.+',
|
||||
'locale\.changed',
|
||||
'clockwork\..+'
|
||||
];
|
||||
}
|
||||
}
|
250
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelNotificationsDataSource.php
vendored
Normal file
250
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelNotificationsDataSource.php
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Events\MessageSending;
|
||||
use Illuminate\Mail\Events\MessageSent;
|
||||
use Illuminate\Notifications\Events\NotificationSending;
|
||||
use Illuminate\Notifications\Events\NotificationSent;
|
||||
|
||||
// Data source for Laravel notifications and mail components, provides sent notifications and emails
|
||||
class LaravelNotificationsDataSource extends DataSource
|
||||
{
|
||||
// Event dispatcher instance
|
||||
protected $dispatcher;
|
||||
|
||||
// Sent notifications
|
||||
protected $notifications = [];
|
||||
|
||||
// Last collected notification
|
||||
protected $lastNotification;
|
||||
|
||||
// Create a new data source instance, takes an event dispatcher as argument
|
||||
public function __construct(Dispatcher $dispatcher)
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
// Add sent notifications to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->notifications = array_merge($request->notifications, $this->notifications);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->notifications = [];
|
||||
}
|
||||
|
||||
// Listen to the email and notification events
|
||||
public function listenToEvents()
|
||||
{
|
||||
$this->dispatcher->listen(MessageSending::class, function ($event) { $this->sendingMessage($event); });
|
||||
$this->dispatcher->listen(MessageSent::class, function ($event) { $this->sentMessage($event); });
|
||||
|
||||
$this->dispatcher->listen(NotificationSending::class, function ($event) { $this->sendingNotification($event); });
|
||||
$this->dispatcher->listen(NotificationSent::class, function ($event) { $this->sentNotification($event); });
|
||||
}
|
||||
|
||||
// Collect a sent email
|
||||
protected function sendingMessage($event)
|
||||
{
|
||||
$trace = StackTrace::get()->resolveViewName();
|
||||
|
||||
$mailable = ($frame = $trace->first(function ($frame) { return is_subclass_of($frame->object, Mailable::class); }))
|
||||
? $frame->object : null;
|
||||
|
||||
$notification = (object) [
|
||||
'subject' => $event->message->getSubject(),
|
||||
'from' => $this->messageAddressToString($event->message->getFrom()),
|
||||
'to' => $this->messageAddressToString($event->message->getTo()),
|
||||
'content' => $this->messageBody($event->message),
|
||||
'type' => 'mail',
|
||||
'data' => [
|
||||
'cc' => $this->messageAddressToString($event->message->getCc()),
|
||||
'bcc' => $this->messageAddressToString($event->message->getBcc()),
|
||||
'replyTo' => $this->messageAddressToString($event->message->getReplyTo()),
|
||||
'mailable' => (new Serializer)->normalize($mailable)
|
||||
],
|
||||
'time' => microtime(true),
|
||||
'trace' => (new Serializer)->trace($trace)
|
||||
];
|
||||
|
||||
if ($this->updateLastNotification($notification)) return;
|
||||
|
||||
if ($this->passesFilters([ $notification ])) {
|
||||
$this->notifications[] = $this->lastNotification = $notification;
|
||||
} else {
|
||||
$this->lastNotification = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update last notification with time taken to send it
|
||||
protected function sentMessage($event)
|
||||
{
|
||||
if ($this->lastNotification) {
|
||||
$this->lastNotification->duration = (microtime(true) - $this->lastNotification->time) * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect a sent notification
|
||||
protected function sendingNotification($event)
|
||||
{
|
||||
$trace = StackTrace::get()->resolveViewName();
|
||||
|
||||
$channelSpecific = $this->resolveChannelSpecific($event);
|
||||
|
||||
$notification = (object) [
|
||||
'subject' => $channelSpecific['subject'],
|
||||
'from' => $channelSpecific['from'],
|
||||
'to' => $channelSpecific['to'],
|
||||
'content' => $channelSpecific['content'],
|
||||
'type' => $event->channel,
|
||||
'data' => array_merge($channelSpecific['data'], [
|
||||
'notification' => (new Serializer)->normalize($event->notification),
|
||||
'notifiable' => (new Serializer)->normalize($event->notifiable)
|
||||
]),
|
||||
'time' => microtime(true),
|
||||
'trace' => (new Serializer)->trace($trace)
|
||||
];
|
||||
|
||||
if ($this->passesFilters([ $notification ])) {
|
||||
$this->notifications[] = $this->lastNotification = $notification;
|
||||
} else {
|
||||
$this->lastNotification = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update last notification with time taken to send it and response
|
||||
protected function sentNotification($event)
|
||||
{
|
||||
if ($this->lastNotification) {
|
||||
$this->lastNotification->duration = (microtime(true) - $this->lastNotification->time) * 1000;
|
||||
$this->lastNotification->data['response'] = $event->response;
|
||||
}
|
||||
}
|
||||
|
||||
// Update last sent email notification with additional data from the message sent event
|
||||
protected function updateLastNotification($notification)
|
||||
{
|
||||
if (! $this->lastNotification) return false;
|
||||
|
||||
if ($this->lastNotification->to !== $notification->to) return false;
|
||||
|
||||
$this->lastNotification->subject = $notification->subject;
|
||||
$this->lastNotification->from = $notification->from;
|
||||
$this->lastNotification->to = $notification->to;
|
||||
$this->lastNotification->content = $notification->content;
|
||||
|
||||
$this->lastNotification->data = array_merge($this->lastNotification->data, $notification->data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Resolve notification channel specific data
|
||||
protected function resolveChannelSpecific($event)
|
||||
{
|
||||
if (method_exists($event->notification, 'toMail')) {
|
||||
$channelSpecific = $this->resolveMailChannelSpecific($event, $event->notification->toMail($event->notifiable));
|
||||
} elseif (method_exists($event->notification, 'toSlack')) {
|
||||
$channelSpecific = $this->resolveSlackChannelSpecific($event, $event->notification->toSlack($event->notifiable));
|
||||
} elseif (method_exists($event->notification, 'toNexmo')) {
|
||||
$channelSpecific = $this->resolveNexmoChannelSpecific($event, $event->notification->toNexmo($event->notifiable));
|
||||
} elseif (method_exists($event->notification, 'toBroadcast')) {
|
||||
$channelSpecific = [ 'data' => [ 'data' => (new Serializer)->normalize($event->notification->toBroadcast($event->notifiable)) ] ];
|
||||
} elseif (method_exists($event->notification, 'toArray')) {
|
||||
$channelSpecific = [ 'data' => [ 'data' => (new Serializer)->normalize($event->notification->toArray($event->notifiable)) ] ];
|
||||
} else {
|
||||
$channelSpecific = [];
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
[ 'subject' => null, 'from' => null, 'to' => null, 'content' => null, 'data' => [] ], $channelSpecific
|
||||
);
|
||||
}
|
||||
|
||||
// Resolve mail notification channel specific data
|
||||
protected function resolveMailChannelSpecific($event, $message)
|
||||
{
|
||||
return [
|
||||
'subject' => $message->subject ?: get_class($event->notification),
|
||||
'from' => $this->notificationAddressToString($message->from),
|
||||
'to' => $this->notificationAddressToString($event->notifiable->routeNotificationFor('mail', $event->notification)),
|
||||
'data' => [
|
||||
'cc' => $this->notificationAddressToString($message->cc),
|
||||
'bcc' => $this->notificationAddressToString($message->bcc),
|
||||
'replyTo' => $this->notificationAddressToString($message->replyTo)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// Resolve Slack notification channel specific data
|
||||
protected function resolveSlackChannelSpecific($event, $message)
|
||||
{
|
||||
return [
|
||||
'subject' => get_class($event->notification),
|
||||
'from' => $message->username,
|
||||
'to' => $message->channel,
|
||||
'content' => $message->content
|
||||
];
|
||||
}
|
||||
|
||||
// Resolve Nexmo notification channel specific data
|
||||
protected function resolveNexmoChannelSpecific($event, $message)
|
||||
{
|
||||
return [
|
||||
'subject' => get_class($event->notification),
|
||||
'from' => $message->from,
|
||||
'to' => $event->notifiable->routeNotificationFor('nexmo', $event->notification),
|
||||
'content' => $message->content
|
||||
];
|
||||
}
|
||||
|
||||
protected function messageAddressToString($address)
|
||||
{
|
||||
if (! $address) return;
|
||||
|
||||
return array_map(function ($address, $key) {
|
||||
// Laravel 8 or earlier
|
||||
if (! ($address instanceof \Symfony\Component\Mime\Address)) {
|
||||
return $address ? "{$address} <{$key}>" : $key;
|
||||
}
|
||||
|
||||
// Laravel 9 or later
|
||||
return $address->toString();
|
||||
}, $address, array_keys($address));
|
||||
}
|
||||
|
||||
protected function messageBody($message)
|
||||
{
|
||||
// Laravel 8 or earlier
|
||||
if (! ($message instanceof \Symfony\Component\Mime\Email)) {
|
||||
return $message->getBody();
|
||||
}
|
||||
|
||||
// Laravel 9 or later
|
||||
return $message->getHtmlBody() ?: $message->getTextBody();
|
||||
}
|
||||
|
||||
protected function notificationAddressToString($address)
|
||||
{
|
||||
if (! $address) return;
|
||||
if (! is_array($address)) $address = [ $address ];
|
||||
|
||||
return array_map(function ($address) {
|
||||
if (! is_array($address)) return $address;
|
||||
|
||||
$email = isset($address['address']) ? $address['address'] : $address[0];
|
||||
$name = isset($address['name']) ? $address['name'] : $address[1];
|
||||
|
||||
return $name ? "{$name} <{$email}>" : $email;
|
||||
}, $address);
|
||||
}
|
||||
}
|
90
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelQueueDataSource.php
vendored
Normal file
90
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelQueueDataSource.php
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Illuminate\Queue\Queue;
|
||||
|
||||
// Data source for Laravel queue component, provides dispatched queue jobs
|
||||
class LaravelQueueDataSource extends DataSource
|
||||
{
|
||||
// Queue instance
|
||||
protected $queue;
|
||||
|
||||
// Dispatched queue jobs
|
||||
protected $jobs = [];
|
||||
|
||||
// Clockwork ID of the current request
|
||||
protected $currentRequestId;
|
||||
|
||||
// Create a new data source instance, takes a queue as an argument
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
// Adds dispatched queue jobs to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->queueJobs = array_merge($request->queueJobs, $this->getJobs());
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->jobs = [];
|
||||
}
|
||||
|
||||
// Listen to the queue events
|
||||
public function listenToEvents()
|
||||
{
|
||||
$this->queue->createPayloadUsing(function ($connection, $queue, $payload) {
|
||||
$this->registerJob([
|
||||
'id' => $id = (new Request)->id,
|
||||
'connection' => $connection,
|
||||
'queue' => $queue,
|
||||
'name' => $payload['displayName'],
|
||||
'data' => isset($payload['data']['command']) ? $payload['data']['command'] : null,
|
||||
'maxTries' => $payload['maxTries'],
|
||||
'timeout' => $payload['timeout'],
|
||||
'time' => microtime(true)
|
||||
]);
|
||||
|
||||
return [ 'clockwork_id' => $id, 'clockwork_parent_id' => $this->currentRequestId ];
|
||||
});
|
||||
}
|
||||
|
||||
// Set Clockwork ID of the current request
|
||||
public function setCurrentRequestId($requestId)
|
||||
{
|
||||
$this->currentRequestId = $requestId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Collect a dispatched queue job
|
||||
protected function registerJob(array $job)
|
||||
{
|
||||
$trace = StackTrace::get()->resolveViewName();
|
||||
|
||||
$job = array_merge($job, [
|
||||
'trace' => (new Serializer)->trace($trace)
|
||||
]);
|
||||
|
||||
if ($this->passesFilters([ $job ])) {
|
||||
$this->jobs[] = $job;
|
||||
}
|
||||
}
|
||||
|
||||
// Get an array of dispatched queue jobs commands
|
||||
protected function getJobs()
|
||||
{
|
||||
return array_map(function ($query) {
|
||||
return array_merge($query, [
|
||||
'data' => isset($query['data']) ? (new Serializer)->normalize($query['data']) : null
|
||||
]);
|
||||
}, $this->jobs);
|
||||
}
|
||||
}
|
86
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelRedisDataSource.php
vendored
Normal file
86
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelRedisDataSource.php
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
|
||||
|
||||
// Data source for Laravel redis component, provides redis commands
|
||||
class LaravelRedisDataSource extends DataSource
|
||||
{
|
||||
// Event dispatcher instance
|
||||
protected $eventDispatcher;
|
||||
|
||||
// Executed redis commands
|
||||
protected $commands = [];
|
||||
|
||||
// Whether to skip Redis commands originating from Laravel cache Redis store
|
||||
protected $skipCacheCommands = true;
|
||||
|
||||
// Create a new data source instance, takes an event dispatcher and additional options as arguments
|
||||
public function __construct(EventDispatcher $eventDispatcher, $skipCacheCommands = true)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
|
||||
$this->skipCacheCommands = $skipCacheCommands;
|
||||
|
||||
if ($this->skipCacheCommands) {
|
||||
$this->addFilter(function ($command, $trace) {
|
||||
return ! $trace->first(function ($frame) { return $frame->class == 'Illuminate\Cache\RedisStore'; });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Adds redis commands to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->redisCommands = array_merge($request->redisCommands, $this->getCommands());
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->commands = [];
|
||||
}
|
||||
|
||||
// Listen to the cache events
|
||||
public function listenToEvents()
|
||||
{
|
||||
$this->eventDispatcher->listen(\Illuminate\Redis\Events\CommandExecuted::class, function ($event) {
|
||||
$this->registerCommand([
|
||||
'command' => $event->command,
|
||||
'parameters' => $event->parameters,
|
||||
'duration' => $event->time,
|
||||
'connection' => $event->connectionName,
|
||||
'time' => microtime(true) - $event->time / 1000
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
// Collect an executed command
|
||||
protected function registerCommand(array $command)
|
||||
{
|
||||
$trace = StackTrace::get()->resolveViewName();
|
||||
|
||||
$command = array_merge($command, [
|
||||
'trace' => (new Serializer)->trace($trace)
|
||||
]);
|
||||
|
||||
if ($this->passesFilters([ $command, $trace ])) {
|
||||
$this->commands[] = $command;
|
||||
}
|
||||
}
|
||||
|
||||
// Get an array of executed redis commands
|
||||
protected function getCommands()
|
||||
{
|
||||
return array_map(function ($query) {
|
||||
return array_merge($query, [
|
||||
'parameters' => isset($query['parameters']) ? (new Serializer)->normalize($query['parameters']) : null
|
||||
]);
|
||||
}, $this->commands);
|
||||
}
|
||||
}
|
71
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelViewsDataSource.php
vendored
Normal file
71
vendor/itsgoingd/clockwork/Clockwork/DataSource/LaravelViewsDataSource.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\DataSource\DataSource;
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Request\Timeline\Timeline;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
// Data source for Laravel views component, provides rendered views
|
||||
class LaravelViewsDataSource extends DataSource
|
||||
{
|
||||
// Event dispatcher
|
||||
protected $dispatcher;
|
||||
|
||||
// Timeline data structure for collected views
|
||||
protected $views;
|
||||
|
||||
// Whether we should collect view data
|
||||
protected $collectData = false;
|
||||
|
||||
// Create a new data source instance, takes an event dispatcher as argument
|
||||
public function __construct(Dispatcher $dispatcher, $collectData = false)
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
|
||||
$this->collectData = $collectData;
|
||||
|
||||
$this->views = new Timeline;
|
||||
}
|
||||
|
||||
// Adds rendered views to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->viewsData = array_merge($request->viewsData, $this->views->finalize());
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->views = new Timeline;
|
||||
}
|
||||
|
||||
// Listen to the views events
|
||||
public function listenToEvents()
|
||||
{
|
||||
$this->dispatcher->listen('composing:*', function ($view, $data = null) {
|
||||
if (is_string($view) && is_array($data)) { // Laravel 5.4 wildcard event
|
||||
$view = $data[0];
|
||||
}
|
||||
|
||||
$data = array_filter(
|
||||
$this->collectData ? $view->getData() : [],
|
||||
function ($v, $k) { return strpos($k, '__') !== 0; },
|
||||
\ARRAY_FILTER_USE_BOTH
|
||||
);
|
||||
|
||||
$this->views->event('Rendering a view', [
|
||||
'name' => 'view ' . $view->getName(),
|
||||
'start' => $time = microtime(true),
|
||||
'end' => $time,
|
||||
'data' => [
|
||||
'name' => $view->getName(),
|
||||
'data' => (new Serializer)->normalize($data)
|
||||
]
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
201
vendor/itsgoingd/clockwork/Clockwork/DataSource/LumenDataSource.php
vendored
Normal file
201
vendor/itsgoingd/clockwork/Clockwork/DataSource/LumenDataSource.php
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\DataSource\DataSource;
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Request\Log;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Laravel\Lumen\Application;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
// Data source for Lumen framework, provides application log, request and response information
|
||||
class LumenDataSource extends DataSource
|
||||
{
|
||||
// Lumen application instance
|
||||
protected $app;
|
||||
|
||||
// Lumen response instance
|
||||
protected $response;
|
||||
|
||||
// Whether we should collect log messages
|
||||
protected $collectLog = true;
|
||||
|
||||
// Whether we should collect routes
|
||||
protected $collectRoutes = false;
|
||||
|
||||
// Clockwork log instance
|
||||
protected $log;
|
||||
|
||||
// Create a new data source, takes Lumen application instance and additional options as arguments
|
||||
public function __construct(Application $app, $collectLog = true, $collectRoutes = false)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
||||
$this->collectLog = $collectLog;
|
||||
$this->collectRoutes = $collectRoutes;
|
||||
|
||||
$this->log = new Log;
|
||||
}
|
||||
|
||||
// Adds request, response information, middleware, routes, session data, user and log entries to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->method = $this->getRequestMethod();
|
||||
$request->uri = $this->getRequestUri();
|
||||
$request->controller = $this->getController();
|
||||
$request->headers = $this->getRequestHeaders();
|
||||
$request->responseStatus = $this->getResponseStatus();
|
||||
$request->routes = $this->getRoutes();
|
||||
$request->sessionData = $this->getSessionData();
|
||||
|
||||
$this->resolveAuthenticatedUser($request);
|
||||
|
||||
$request->log()->merge($this->log);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->log = new Log;
|
||||
}
|
||||
|
||||
// Set Lumen response instance for the current request
|
||||
public function setResponse(Response $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Listen for the log events
|
||||
public function listenToEvents()
|
||||
{
|
||||
if (! $this->collectLog) return;
|
||||
|
||||
if (class_exists(\Illuminate\Log\Events\MessageLogged::class)) {
|
||||
// Lumen 5.4
|
||||
$this->app['events']->listen(\Illuminate\Log\Events\MessageLogged::class, function ($event) {
|
||||
$this->log->log($event->level, $event->message, $event->context);
|
||||
});
|
||||
} else {
|
||||
// Lumen 5.0 to 5.3
|
||||
$this->app['events']->listen('illuminate.log', function ($level, $message, $context) {
|
||||
$this->log->log($level, $message, $context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get a textual representation of current route's controller
|
||||
protected function getController()
|
||||
{
|
||||
$routes = method_exists($this->app, 'getRoutes') ? $this->app->getRoutes() : [];
|
||||
|
||||
$method = $this->getRequestMethod();
|
||||
$pathInfo = $this->getPathInfo();
|
||||
|
||||
if (isset($routes[$method.$pathInfo]['action']['uses'])) {
|
||||
$controller = $routes[$method.$pathInfo]['action']['uses'];
|
||||
} elseif (isset($routes[$method.$pathInfo]['action'][0])) {
|
||||
$controller = $routes[$method.$pathInfo]['action'][0];
|
||||
} else {
|
||||
$controller = null;
|
||||
}
|
||||
|
||||
if ($controller instanceof \Closure) {
|
||||
$controller = 'anonymous function';
|
||||
} elseif (is_object($controller)) {
|
||||
$controller = 'instance of ' . get_class($controller);
|
||||
} elseif (! is_string($controller)) {
|
||||
$controller = null;
|
||||
}
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
// Get the request headers
|
||||
protected function getRequestHeaders()
|
||||
{
|
||||
return $this->app['request']->headers->all();
|
||||
}
|
||||
|
||||
// Get the request method
|
||||
protected function getRequestMethod()
|
||||
{
|
||||
if ($this->app->bound('request')) {
|
||||
return $this->app['request']->getMethod();
|
||||
} elseif (isset($_POST['_method'])) {
|
||||
return strtoupper($_POST['_method']);
|
||||
} else {
|
||||
return $_SERVER['REQUEST_METHOD'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the request URI
|
||||
protected function getRequestUri()
|
||||
{
|
||||
return $this->app['request']->getRequestUri();
|
||||
}
|
||||
|
||||
// Get the response status code
|
||||
protected function getResponseStatus()
|
||||
{
|
||||
return $this->response ? $this->response->getStatusCode() : null;
|
||||
}
|
||||
|
||||
// Get an array of application routes
|
||||
protected function getRoutes()
|
||||
{
|
||||
if (! $this->collectRoutes) return [];
|
||||
|
||||
if (isset($this->app->router)) {
|
||||
$routes = array_values($this->app->router->getRoutes());
|
||||
} elseif (method_exists($this->app, 'getRoutes')) {
|
||||
$routes = array_values($this->app->getRoutes());
|
||||
} else {
|
||||
$routes = [];
|
||||
}
|
||||
|
||||
return array_map(function ($route) {
|
||||
return [
|
||||
'method' => $route['method'],
|
||||
'uri' => $route['uri'],
|
||||
'name' => isset($route['action']['as']) ? $route['action']['as'] : null,
|
||||
'action' => isset($route['action']['uses']) && is_string($route['action']['uses']) ? $route['action']['uses'] : 'anonymous function',
|
||||
'middleware' => isset($route['action']['middleware']) ? $route['action']['middleware'] : null,
|
||||
];
|
||||
}, $routes);
|
||||
}
|
||||
|
||||
// Get the session data (normalized with passwords removed)
|
||||
protected function getSessionData()
|
||||
{
|
||||
if (! isset($this->app['session'])) return [];
|
||||
|
||||
return $this->removePasswords((new Serializer)->normalizeEach($this->app['session']->all()));
|
||||
}
|
||||
|
||||
// Add authenticated user data to the request
|
||||
protected function resolveAuthenticatedUser(Request $request)
|
||||
{
|
||||
if (! isset($this->app['auth'])) return;
|
||||
if (! ($user = $this->app['auth']->user())) return;
|
||||
if (! isset($user->email) || ! isset($user->id)) return;
|
||||
|
||||
$request->setAuthenticatedUser($user->email, $user->id, [
|
||||
'email' => $user->email,
|
||||
'name' => isset($user->name) ? $user->name : null
|
||||
]);
|
||||
}
|
||||
|
||||
// Get the request path info
|
||||
protected function getPathInfo()
|
||||
{
|
||||
if ($this->app->bound('request')) {
|
||||
return $this->app['request']->getPathInfo();
|
||||
} else {
|
||||
$query = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
|
||||
return '/' . trim(str_replace("?{$query}", '', $_SERVER['REQUEST_URI']), '/');
|
||||
}
|
||||
}
|
||||
}
|
37
vendor/itsgoingd/clockwork/Clockwork/DataSource/MonologDataSource.php
vendored
Normal file
37
vendor/itsgoingd/clockwork/Clockwork/DataSource/MonologDataSource.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\DataSource\DataSource;
|
||||
use Clockwork\Request\Log;
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Support\Monolog\Handler\ClockworkHandler;
|
||||
|
||||
use Monolog\Logger as Monolog;
|
||||
|
||||
// Data source for Monolog, provides application log
|
||||
class MonologDataSource extends DataSource
|
||||
{
|
||||
// Clockwork log instance
|
||||
protected $log;
|
||||
|
||||
// Create a new data source, takes Monolog instance as an argument
|
||||
public function __construct(Monolog $monolog)
|
||||
{
|
||||
$this->log = new Log;
|
||||
|
||||
$monolog->pushHandler(new ClockworkHandler($this->log));
|
||||
}
|
||||
|
||||
// Adds log entries to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->log()->merge($this->log);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->log = new Log;
|
||||
}
|
||||
}
|
155
vendor/itsgoingd/clockwork/Clockwork/DataSource/PhpDataSource.php
vendored
Normal file
155
vendor/itsgoingd/clockwork/Clockwork/DataSource/PhpDataSource.php
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\DataSource\DataSource;
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
// Data source providing data obtainable in vanilla PHP
|
||||
class PhpDataSource extends DataSource
|
||||
{
|
||||
// Adds request, response information, session data and peak memory usage to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->time = PHP_SAPI !== 'cli' ? $this->getRequestTime() : $request->time;
|
||||
$request->method = $this->getRequestMethod();
|
||||
$request->url = $this->getRequestUrl();
|
||||
$request->uri = $this->getRequestUri();
|
||||
$request->headers = $this->getRequestHeaders();
|
||||
$request->getData = $this->getGetData();
|
||||
$request->postData = $this->getPostData();
|
||||
$request->requestData = $this->getRequestData();
|
||||
$request->sessionData = $this->getSessionData();
|
||||
$request->cookies = $this->getCookies();
|
||||
$request->responseStatus = $this->getResponseStatus();
|
||||
$request->responseTime = $this->getResponseTime();
|
||||
$request->memoryUsage = $this->getMemoryUsage();
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Get the request cookies (normalized with passwords removed)
|
||||
protected function getCookies()
|
||||
{
|
||||
return $this->removePasswords((new Serializer)->normalizeEach($_COOKIE));
|
||||
}
|
||||
|
||||
// Get the request GET data (normalized with passwords removed)
|
||||
protected function getGetData()
|
||||
{
|
||||
return $this->removePasswords((new Serializer)->normalizeEach($_GET));
|
||||
}
|
||||
|
||||
// Get the request POST data (normalized with passwords removed)
|
||||
protected function getPostData()
|
||||
{
|
||||
return $this->removePasswords((new Serializer)->normalizeEach($_POST));
|
||||
}
|
||||
|
||||
// Get the request body data (attempt to parse as json, normalized with passwords removed)
|
||||
protected function getRequestData()
|
||||
{
|
||||
// The data will already be parsed into POST data by PHP in case of application/x-www-form-urlencoded requests
|
||||
if (count($_POST)) return;
|
||||
|
||||
$requestData = file_get_contents('php://input');
|
||||
$requestJsonData = json_decode($requestData, true);
|
||||
|
||||
return is_array($requestJsonData)
|
||||
? $this->removePasswords((new Serializer)->normalizeEach($requestJsonData))
|
||||
: $requestData;
|
||||
}
|
||||
|
||||
// Get the request headers
|
||||
protected function getRequestHeaders()
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (substr($key, 0, 5) !== 'HTTP_') continue;
|
||||
|
||||
$header = substr($key, 5);
|
||||
$header = str_replace('_', ' ', $header);
|
||||
$header = ucwords(strtolower($header));
|
||||
$header = str_replace(' ', '-', $header);
|
||||
|
||||
if (! isset($headers[$header])) {
|
||||
$headers[$header] = [ $value ];
|
||||
} else {
|
||||
$headers[$header][] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($headers);
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
// Get the request method
|
||||
protected function getRequestMethod()
|
||||
{
|
||||
if (isset($_SERVER['REQUEST_METHOD'])) {
|
||||
return $_SERVER['REQUEST_METHOD'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the response time
|
||||
protected function getRequestTime()
|
||||
{
|
||||
if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
|
||||
return $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the request URL
|
||||
protected function getRequestUrl()
|
||||
{
|
||||
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on';
|
||||
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
|
||||
$addr = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : null;
|
||||
$port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : null;
|
||||
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null;
|
||||
|
||||
$scheme = $https ? 'https' : 'http';
|
||||
$host = $host ?: $addr;
|
||||
$port = (! $https && $port != 80 || $https && $port != 443) ? ":{$port}" : '';
|
||||
|
||||
// remove port number from the host
|
||||
$host = $host ? preg_replace('/:\d+$/', '', trim($host)) : null;
|
||||
|
||||
return "{$scheme}://{$host}{$port}{$uri}";
|
||||
}
|
||||
|
||||
// Get the request URI
|
||||
protected function getRequestUri()
|
||||
{
|
||||
if (isset($_SERVER['REQUEST_URI'])) {
|
||||
return $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the response status code
|
||||
protected function getResponseStatus()
|
||||
{
|
||||
return http_response_code();
|
||||
}
|
||||
|
||||
// Get the response time (current time, assuming most of the application code has already run at this point)
|
||||
protected function getResponseTime()
|
||||
{
|
||||
return microtime(true);
|
||||
}
|
||||
|
||||
// Get the session data (normalized with passwords removed)
|
||||
protected function getSessionData()
|
||||
{
|
||||
if (! isset($_SESSION)) return [];
|
||||
|
||||
return $this->removePasswords((new Serializer)->normalizeEach($_SESSION));
|
||||
}
|
||||
|
||||
// Get the peak memory usage in bytes
|
||||
protected function getMemoryUsage()
|
||||
{
|
||||
return memory_get_peak_usage(true);
|
||||
}
|
||||
}
|
96
vendor/itsgoingd/clockwork/Clockwork/DataSource/PsrMessageDataSource.php
vendored
Normal file
96
vendor/itsgoingd/clockwork/Clockwork/DataSource/PsrMessageDataSource.php
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\DataSource\DataSource;
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as PsrRequest;
|
||||
use Psr\Http\Message\ResponseInterface as PsrResponse;
|
||||
|
||||
// Data source providing data obtainable from the PSR-7 request and response interfaces
|
||||
class PsrMessageDataSource extends DataSource
|
||||
{
|
||||
// PSR Messages
|
||||
protected $psrRequest;
|
||||
protected $psrResponse;
|
||||
|
||||
// Create a new data source, takes PSR-7 request and response as arguments
|
||||
public function __construct(PsrRequest $psrRequest = null, PsrResponse $psrResponse = null)
|
||||
{
|
||||
$this->psrRequest = $psrRequest;
|
||||
$this->psrResponse = $psrResponse;
|
||||
}
|
||||
|
||||
// Adds request and response information to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
if ($this->psrRequest) {
|
||||
$request->method = $this->psrRequest->getMethod();
|
||||
$request->uri = $this->getRequestUri();
|
||||
$request->headers = $this->getRequestHeaders();
|
||||
$request->getData = $this->sanitize($this->psrRequest->getQueryParams());
|
||||
$request->postData = $this->sanitize($this->psrRequest->getParsedBody());
|
||||
$request->cookies = $this->sanitize($this->psrRequest->getCookieParams());
|
||||
$request->time = $this->getRequestTime();
|
||||
}
|
||||
|
||||
if ($this->psrResponse !== null) {
|
||||
$request->responseStatus = $this->psrResponse->getStatusCode();
|
||||
$request->responseTime = $this->getResponseTime();
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Normalize items in the array and remove passwords
|
||||
protected function sanitize($data)
|
||||
{
|
||||
return is_array($data) ? $this->removePasswords((new Serializer)->normalizeEach($data)) : $data;
|
||||
}
|
||||
|
||||
// Get the response time, fetching it from ServerParams
|
||||
protected function getRequestTime()
|
||||
{
|
||||
$env = $this->psrRequest->getServerParams();
|
||||
|
||||
if (isset($env['REQUEST_TIME_FLOAT'])) {
|
||||
return $env['REQUEST_TIME_FLOAT'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the response time (current time, assuming most of the application code has already run at this point)
|
||||
protected function getResponseTime()
|
||||
{
|
||||
return microtime(true);
|
||||
}
|
||||
|
||||
// Get the request headers
|
||||
protected function getRequestHeaders()
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach ($this->psrRequest->getHeaders() as $header => $values) {
|
||||
if (strtoupper(substr($header, 0, 5)) === 'HTTP_') {
|
||||
$header = substr($header, 5);
|
||||
}
|
||||
|
||||
$header = str_replace('_', ' ', $header);
|
||||
$header = ucwords(strtolower($header));
|
||||
$header = str_replace(' ', '-', $header);
|
||||
|
||||
$headers[$header] = $values;
|
||||
}
|
||||
|
||||
ksort($headers);
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
// Get the request URI
|
||||
protected function getRequestUri()
|
||||
{
|
||||
$uri = $this->psrRequest->getUri();
|
||||
|
||||
return $uri->getPath() . ($uri->getQuery() ? '?' . $uri->getQuery() : '');
|
||||
}
|
||||
}
|
104
vendor/itsgoingd/clockwork/Clockwork/DataSource/SlimDataSource.php
vendored
Normal file
104
vendor/itsgoingd/clockwork/Clockwork/DataSource/SlimDataSource.php
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\DataSource\DataSource;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use Slim\Slim;
|
||||
|
||||
// Data source for Slim 2 framework, provides controller, request and response information
|
||||
class SlimDataSource extends DataSource
|
||||
{
|
||||
// Slim instance
|
||||
protected $slim;
|
||||
|
||||
// Create a new data source, takes Slim instance as an argument
|
||||
public function __construct(Slim $slim)
|
||||
{
|
||||
$this->slim = $slim;
|
||||
}
|
||||
|
||||
// Adds request and response information to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->method = $this->getRequestMethod();
|
||||
$request->uri = $this->getRequestUri();
|
||||
$request->controller = $this->getController();
|
||||
$request->headers = $this->getRequestHeaders();
|
||||
$request->responseStatus = $this->getResponseStatus();
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Get a textual representation of current route's controller
|
||||
protected function getController()
|
||||
{
|
||||
$matchedRoutes = $this->slim->router()->getMatchedRoutes(
|
||||
$this->slim->request()->getMethod(), $this->slim->request()->getResourceUri()
|
||||
);
|
||||
|
||||
if (! count($matchedRoutes)) return;
|
||||
|
||||
$controller = end($matchedRoutes)->getCallable();
|
||||
|
||||
if ($controller instanceof \Closure) {
|
||||
$controller = 'anonymous function';
|
||||
} elseif (is_object($controller)) {
|
||||
$controller = 'instance of ' . get_class($controller);
|
||||
} elseif (is_array($controller) && count($controller) == 2) {
|
||||
if (is_object($controller[0])) {
|
||||
$controller = get_class($controller[0]) . '->' . $controller[1];
|
||||
} else {
|
||||
$controller = $controller[0] . '::' . $controller[1];
|
||||
}
|
||||
} elseif (! is_string($controller)) {
|
||||
$controller = null;
|
||||
}
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
// Get the request headers
|
||||
protected function getRequestHeaders()
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (substr($key, 0, 5) !== 'HTTP_') continue;
|
||||
|
||||
$header = substr($key, 5);
|
||||
$header = str_replace('_', ' ', $header);
|
||||
$header = ucwords(strtolower($header));
|
||||
$header = str_replace(' ', '-', $header);
|
||||
|
||||
$value = $this->slim->request()->headers($header, $value);
|
||||
|
||||
if (! isset($headers[$header])) {
|
||||
$headers[$header] = [ $value ];
|
||||
} else {
|
||||
$headers[$header][] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($headers);
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
// Get the request method
|
||||
protected function getRequestMethod()
|
||||
{
|
||||
return $this->slim->request()->getMethod();
|
||||
}
|
||||
|
||||
// Get the request URI
|
||||
protected function getRequestUri()
|
||||
{
|
||||
return $this->slim->request()->getPathInfo();
|
||||
}
|
||||
|
||||
// Get the response status code
|
||||
protected function getResponseStatus()
|
||||
{
|
||||
return $this->slim->response()->status();
|
||||
}
|
||||
}
|
45
vendor/itsgoingd/clockwork/Clockwork/DataSource/SwiftDataSource.php
vendored
Normal file
45
vendor/itsgoingd/clockwork/Clockwork/DataSource/SwiftDataSource.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Request\Timeline\Timeline;
|
||||
use Clockwork\Support\Swift\SwiftPluginClockworkTimeline;
|
||||
|
||||
use Swift_Mailer;
|
||||
|
||||
// Data source for Swift mailer, provides sent emails
|
||||
class SwiftDataSource extends DataSource
|
||||
{
|
||||
// Swift instance
|
||||
protected $swift;
|
||||
|
||||
// Clockwork timeline instance
|
||||
protected $timeline;
|
||||
|
||||
// Create a new data source, takes a Swift instance as an argument
|
||||
public function __construct(Swift_Mailer $swift)
|
||||
{
|
||||
$this->swift = $swift;
|
||||
|
||||
$this->timeline = new Timeline;
|
||||
}
|
||||
|
||||
// Listen to the email events
|
||||
public function listenToEvents()
|
||||
{
|
||||
$this->swift->registerPlugin(new SwiftPluginClockworkTimeline($this->timeline));
|
||||
}
|
||||
|
||||
// Adds send emails to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->emailsData = array_merge($request->emailsData, $this->timeline->finalize());
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Reset the data source to an empty state, clearing any collected data
|
||||
public function reset()
|
||||
{
|
||||
$this->timeline = new Timeline;
|
||||
}
|
||||
}
|
40
vendor/itsgoingd/clockwork/Clockwork/DataSource/TwigDataSource.php
vendored
Normal file
40
vendor/itsgoingd/clockwork/Clockwork/DataSource/TwigDataSource.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Support\Twig\ProfilerClockworkDumper;
|
||||
|
||||
use Twig_Environment;
|
||||
use Twig_Extension_Profiler;
|
||||
use Twig_Profiler_Profile;
|
||||
|
||||
// Data source for Twig, provides rendered views
|
||||
class TwigDataSource extends DataSource
|
||||
{
|
||||
// Twig environment instance
|
||||
protected $twig;
|
||||
|
||||
// Twig profile instance
|
||||
protected $profile;
|
||||
|
||||
// Create a new data source, takes Twig instance as an argument
|
||||
public function __construct(Twig_Environment $twig)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
// Register the Twig profiler extension
|
||||
public function listenToEvents()
|
||||
{
|
||||
$this->twig->addExtension(new Twig_Extension_Profiler($this->profile = new Twig_Profiler_Profile));
|
||||
}
|
||||
|
||||
// Adds rendered views to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$timeline = (new ProfilerClockworkDumper)->dump($this->profile);
|
||||
|
||||
$request->viewsData = array_merge($request->viewsData, $timeline->finalize());
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
31
vendor/itsgoingd/clockwork/Clockwork/DataSource/XdebugDataSource.php
vendored
Normal file
31
vendor/itsgoingd/clockwork/Clockwork/DataSource/XdebugDataSource.php
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php namespace Clockwork\DataSource;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
// Data source for Xdebug, provides profiling data
|
||||
class XdebugDataSource extends DataSource
|
||||
{
|
||||
// Adds profiling data path to the request
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$request->xdebug = [ 'profile' => xdebug_get_profiler_filename() ];
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Extends the request with full profiling data
|
||||
public function extend(Request $request)
|
||||
{
|
||||
$profile = isset($request->xdebug['profile']) ? $request->xdebug['profile'] : null;
|
||||
|
||||
if ($profile && ! preg_match('/\.php$/', $profile) && is_readable($profile)) {
|
||||
$request->xdebug['profileData'] = file_get_contents($profile);
|
||||
|
||||
if (preg_match('/\.gz$/', $profile)) {
|
||||
$request->xdebug['profileData'] = gzdecode($request->xdebug['profileData']);
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
36
vendor/itsgoingd/clockwork/Clockwork/Helpers/Concerns/ResolvesViewName.php
vendored
Normal file
36
vendor/itsgoingd/clockwork/Clockwork/Helpers/Concerns/ResolvesViewName.php
vendored
Normal 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)
|
||||
));
|
||||
}
|
||||
}
|
139
vendor/itsgoingd/clockwork/Clockwork/Helpers/Serializer.php
vendored
Normal file
139
vendor/itsgoingd/clockwork/Clockwork/Helpers/Serializer.php
vendored
Normal 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
|
||||
];
|
||||
}
|
||||
}
|
45
vendor/itsgoingd/clockwork/Clockwork/Helpers/ServerTiming.php
vendored
Normal file
45
vendor/itsgoingd/clockwork/Clockwork/Helpers/ServerTiming.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
148
vendor/itsgoingd/clockwork/Clockwork/Helpers/StackFilter.php
vendored
Normal file
148
vendor/itsgoingd/clockwork/Clockwork/Helpers/StackFilter.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
38
vendor/itsgoingd/clockwork/Clockwork/Helpers/StackFrame.php
vendored
Normal file
38
vendor/itsgoingd/clockwork/Clockwork/Helpers/StackFrame.php
vendored
Normal 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}()";
|
||||
}
|
||||
}
|
||||
}
|
127
vendor/itsgoingd/clockwork/Clockwork/Helpers/StackTrace.php
vendored
Normal file
127
vendor/itsgoingd/clockwork/Clockwork/Helpers/StackTrace.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
20
vendor/itsgoingd/clockwork/Clockwork/Request/IncomingRequest.php
vendored
Normal file
20
vendor/itsgoingd/clockwork/Clockwork/Request/IncomingRequest.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php namespace Clockwork\Request;
|
||||
|
||||
// Incoming HTTP request
|
||||
class IncomingRequest
|
||||
{
|
||||
// Method
|
||||
public $method;
|
||||
// URI
|
||||
public $uri;
|
||||
|
||||
// GET and POST data
|
||||
public $input = [];
|
||||
// Cookies
|
||||
public $cookies = [];
|
||||
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
foreach ($data as $key => $val) $this->$key = $val;
|
||||
}
|
||||
}
|
125
vendor/itsgoingd/clockwork/Clockwork/Request/Log.php
vendored
Normal file
125
vendor/itsgoingd/clockwork/Clockwork/Request/Log.php
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php namespace Clockwork\Request;
|
||||
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
use Clockwork\Helpers\StackFilter;
|
||||
|
||||
// Data structure representing a log with timestamped messages
|
||||
class Log
|
||||
{
|
||||
// Array of logged messages
|
||||
public $messages = [];
|
||||
|
||||
// Create a new log, optionally with existing messages
|
||||
public function __construct($messages = [])
|
||||
{
|
||||
$this->messages = $messages;
|
||||
}
|
||||
|
||||
// Log a new message, with a level and context, context can be used to override serializer defaults,
|
||||
// $context['trace'] = true can be used to force collecting a stack trace
|
||||
public function log($level = LogLevel::INFO, $message = null, array $context = [])
|
||||
{
|
||||
$trace = $this->hasTrace($context) ? $context['trace'] : StackTrace::get()->resolveViewName();
|
||||
|
||||
$this->messages[] = [
|
||||
'message' => (new Serializer($context))->normalize($message),
|
||||
'exception' => $this->formatException($context),
|
||||
'context' => $this->formatContext($context),
|
||||
'level' => $level,
|
||||
'time' => microtime(true),
|
||||
'trace' => (new Serializer(! empty($context['trace']) ? [ 'traces' => true ] : []))->trace($trace)
|
||||
];
|
||||
}
|
||||
|
||||
public function emergency($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::EMERGENCY, $message, $context);
|
||||
}
|
||||
|
||||
public function alert($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::ALERT, $message, $context);
|
||||
}
|
||||
|
||||
public function critical($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::CRITICAL, $message, $context);
|
||||
}
|
||||
|
||||
public function error($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::ERROR, $message, $context);
|
||||
}
|
||||
|
||||
public function warning($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::WARNING, $message, $context);
|
||||
}
|
||||
|
||||
public function notice($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::NOTICE, $message, $context);
|
||||
}
|
||||
|
||||
public function info($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::INFO, $message, $context);
|
||||
}
|
||||
|
||||
public function debug($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::DEBUG, $message, $context);
|
||||
}
|
||||
|
||||
// Merge another log instance into the current log
|
||||
public function merge(Log $log)
|
||||
{
|
||||
$this->messages = array_merge($this->messages, $log->messages);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Sort the log messages by timestamp
|
||||
public function sort()
|
||||
{
|
||||
usort($this->messages, function ($a, $b) { return $a['time'] * 1000 - $b['time'] * 1000; });
|
||||
}
|
||||
|
||||
// Get all messages as an array
|
||||
public function toArray()
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
// Format message context, removes exception and trace if we are serializing them
|
||||
protected function formatContext($context)
|
||||
{
|
||||
if ($this->hasException($context)) unset($context['exception']);
|
||||
if ($this->hasTrace($context)) unset($context['trace']);
|
||||
|
||||
return (new Serializer)->normalize($context);
|
||||
}
|
||||
|
||||
// Format exception if present in the context
|
||||
protected function formatException($context)
|
||||
{
|
||||
if ($this->hasException($context)) {
|
||||
return (new Serializer)->exception($context['exception']);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if context has serializable trace
|
||||
protected function hasTrace($context)
|
||||
{
|
||||
return ! empty($context['trace']) && $context['trace'] instanceof StackTrace && empty($context['raw']);
|
||||
}
|
||||
|
||||
// Check if context has serializable exception
|
||||
protected function hasException($context)
|
||||
{
|
||||
return ! empty($context['exception'])
|
||||
&& ($context['exception'] instanceof \Throwable || $context['exception'] instanceof \Exception)
|
||||
&& empty($context['raw']);
|
||||
}
|
||||
}
|
13
vendor/itsgoingd/clockwork/Clockwork/Request/LogLevel.php
vendored
Normal file
13
vendor/itsgoingd/clockwork/Clockwork/Request/LogLevel.php
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php namespace Clockwork\Request;
|
||||
|
||||
class LogLevel
|
||||
{
|
||||
const EMERGENCY = 'emergency';
|
||||
const ALERT = 'alert';
|
||||
const CRITICAL = 'critical';
|
||||
const ERROR = 'error';
|
||||
const WARNING = 'warning';
|
||||
const NOTICE = 'notice';
|
||||
const INFO = 'info';
|
||||
const DEBUG = 'debug';
|
||||
}
|
591
vendor/itsgoingd/clockwork/Clockwork/Request/Request.php
vendored
Normal file
591
vendor/itsgoingd/clockwork/Clockwork/Request/Request.php
vendored
Normal file
@@ -0,0 +1,591 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
10
vendor/itsgoingd/clockwork/Clockwork/Request/RequestType.php
vendored
Normal file
10
vendor/itsgoingd/clockwork/Clockwork/Request/RequestType.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php namespace Clockwork\Request;
|
||||
|
||||
// Supported request types
|
||||
class RequestType
|
||||
{
|
||||
const REQUEST = 'request';
|
||||
const COMMAND = 'command';
|
||||
const QUEUE_JOB = 'queue-job';
|
||||
const TEST = 'test';
|
||||
}
|
120
vendor/itsgoingd/clockwork/Clockwork/Request/ShouldCollect.php
vendored
Normal file
120
vendor/itsgoingd/clockwork/Clockwork/Request/ShouldCollect.php
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php namespace Clockwork\Request;
|
||||
|
||||
// Filter incoming requests before collecting data
|
||||
class ShouldCollect
|
||||
{
|
||||
// Enable on-demand mode, boolean or the secret value
|
||||
protected $onDemand = false;
|
||||
// Enable sampling, chance to be sampled (eg. 100 to collect 1 in 100 requests)
|
||||
protected $sample = false;
|
||||
|
||||
// List of URIs that should not be collected, can contain regexes
|
||||
protected $except = [];
|
||||
// List of URIs that should only be collected, can contain regexes (only used if non-empty)
|
||||
protected $only = [];
|
||||
|
||||
// Disable collection of OPTIONS method requests (most commonly used for CORS pre-flight requests)
|
||||
protected $exceptPreflight = false;
|
||||
|
||||
// Custom filter callback
|
||||
protected $callback;
|
||||
|
||||
// Append one or more except URIs
|
||||
public function except($uris)
|
||||
{
|
||||
$this->except = array_merge($this->except, is_array($uris) ? $uris : [ $uris ]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Append one or more only URIs
|
||||
public function only($uris)
|
||||
{
|
||||
$this->only = array_merge($this->only, is_array($uris) ? $uris : [ $uris ]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Merge multiple settings from array
|
||||
public function merge(array $data = [])
|
||||
{
|
||||
foreach ($data as $key => $val) $this->$key = $val;
|
||||
}
|
||||
|
||||
// Apply the filter to an incoming request
|
||||
public function filter(IncomingRequest $request)
|
||||
{
|
||||
return $this->passOnDemand($request)
|
||||
&& $this->passSampling()
|
||||
&& $this->passExcept($request)
|
||||
&& $this->passOnly($request)
|
||||
&& $this->passExceptPreflight($request)
|
||||
&& $this->passCallback($request);
|
||||
}
|
||||
|
||||
protected function passOnDemand(IncomingRequest $request)
|
||||
{
|
||||
if (! $this->onDemand) return true;
|
||||
|
||||
if ($this->onDemand !== true) {
|
||||
$input = isset($request->input['clockwork-profile']) ? $request->input['clockwork-profile'] : '';
|
||||
$cookie = isset($request->cookies['clockwork-profile']) ? $request->cookies['clockwork-profile'] : '';
|
||||
|
||||
return hash_equals($this->onDemand, $input) || hash_equals($this->onDemand, $cookie);
|
||||
}
|
||||
|
||||
return isset($request->input['clockwork-profile']) || isset($request->cookies['clockwork-profile']);
|
||||
}
|
||||
|
||||
protected function passSampling()
|
||||
{
|
||||
if (! $this->sample) return true;
|
||||
|
||||
return mt_rand(0, $this->sample) == $this->sample;
|
||||
}
|
||||
|
||||
protected function passExcept(IncomingRequest $request)
|
||||
{
|
||||
if (! count($this->except)) return true;
|
||||
|
||||
foreach ($this->except as $pattern) {
|
||||
if (preg_match('#' . str_replace('#', '\#', $pattern) . '#', $request->uri)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function passOnly(IncomingRequest $request)
|
||||
{
|
||||
if (! count($this->only)) return true;
|
||||
|
||||
foreach ($this->only as $pattern) {
|
||||
if (preg_match('#' . str_replace('#', '\#', $pattern) . '#', $request->uri)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function passExceptPreflight(IncomingRequest $request)
|
||||
{
|
||||
if (! $this->exceptPreflight) return true;
|
||||
|
||||
return strtoupper($request->method) != 'OPTIONS';
|
||||
}
|
||||
|
||||
protected function passCallback(IncomingRequest $request)
|
||||
{
|
||||
if (! $this->callback) return true;
|
||||
|
||||
return call_user_func($this->callback, $request);
|
||||
}
|
||||
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (! count($parameters)) return $this->$method;
|
||||
|
||||
$this->$method = count($parameters) ? $parameters[0] : true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
58
vendor/itsgoingd/clockwork/Clockwork/Request/ShouldRecord.php
vendored
Normal file
58
vendor/itsgoingd/clockwork/Clockwork/Request/ShouldRecord.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php namespace Clockwork\Request;
|
||||
|
||||
// Filter requests before recording
|
||||
class ShouldRecord
|
||||
{
|
||||
// Enable collecting of errors only (requests with 4xx or 5xx responses)
|
||||
protected $errorsOnly = false;
|
||||
// Enable collecting of slow requests only, slow response time threshold in ms
|
||||
protected $slowOnly = false;
|
||||
|
||||
// Custom filter callback
|
||||
protected $callback;
|
||||
|
||||
// Merge multiple settings from array
|
||||
public function merge(array $data = [])
|
||||
{
|
||||
foreach ($data as $key => $val) $this->$key = $val;
|
||||
}
|
||||
|
||||
// Apply the filter to a request
|
||||
public function filter(Request $request)
|
||||
{
|
||||
return $this->passErrorsOnly($request)
|
||||
&& $this->passSlowOnly($request)
|
||||
&& $this->passCallback($request);
|
||||
}
|
||||
|
||||
protected function passErrorsOnly(Request $request)
|
||||
{
|
||||
if (! $this->errorsOnly) return true;
|
||||
|
||||
return 400 <= $request->responseStatus && $request->responseStatus <= 599;
|
||||
}
|
||||
|
||||
protected function passSlowOnly(Request $request)
|
||||
{
|
||||
if (! $this->slowOnly) return true;
|
||||
|
||||
return $request->getResponseDuration() >= $this->slowOnly;
|
||||
}
|
||||
|
||||
protected function passCallback(Request $request)
|
||||
{
|
||||
if (! $this->callback) return true;
|
||||
|
||||
return call_user_func($this->callback, $request);
|
||||
}
|
||||
|
||||
// Fluent API
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (! count($parameters)) return $this->$method;
|
||||
|
||||
$this->$method = count($parameters) ? $parameters[0] : true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
105
vendor/itsgoingd/clockwork/Clockwork/Request/Timeline/Event.php
vendored
Normal file
105
vendor/itsgoingd/clockwork/Clockwork/Request/Timeline/Event.php
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php namespace Clockwork\Request\Timeline;
|
||||
|
||||
// Data structure representing a single timeline event with fluent API
|
||||
class Event
|
||||
{
|
||||
// Event description
|
||||
public $description;
|
||||
// Unique event name
|
||||
public $name;
|
||||
|
||||
// Start time
|
||||
public $start;
|
||||
// End time
|
||||
public $end;
|
||||
|
||||
// Color (blue, red, green, purple, grey)
|
||||
public $color;
|
||||
// Additional event data
|
||||
public $data;
|
||||
|
||||
public function __construct($description, $data = [])
|
||||
{
|
||||
$this->description = $description;
|
||||
$this->name = isset($data['name']) ? $data['name'] : $description;
|
||||
|
||||
$this->start = isset($data['start']) ? $data['start'] : null;
|
||||
$this->end = isset($data['end']) ? $data['end'] : null;
|
||||
|
||||
$this->color = isset($data['color']) ? $data['color'] : null;
|
||||
$this->data = isset($data['data']) ? $data['data'] : null;
|
||||
}
|
||||
|
||||
// Begin the event at current time
|
||||
public function begin()
|
||||
{
|
||||
$this->start = microtime(true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// End the event at current time
|
||||
public function end()
|
||||
{
|
||||
$this->end = microtime(true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Begin the event, execute the passed in closure and end the event, returns the closure return value
|
||||
public function run(\Closure $closure, ...$args)
|
||||
{
|
||||
$this->begin();
|
||||
try {
|
||||
return $closure(...$args);
|
||||
} finally {
|
||||
$this->end();
|
||||
}
|
||||
}
|
||||
|
||||
// Set or retrieve event duration (in ms), event can be defined with both start and end time or just a single time and duration
|
||||
public function duration($duration = null)
|
||||
{
|
||||
if (! $duration) return ($this->start && $this->end) ? ($this->end - $this->start) * 1000 : 0;
|
||||
|
||||
if ($this->start) $this->end = $this->start + $duration / 1000;
|
||||
if ($this->end) $this->start = $this->end - $duration / 1000;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Finalize the event, ends the event, fills in start time if empty and limits the start and end time
|
||||
public function finalize($start = null, $end = null)
|
||||
{
|
||||
$end = $end ?: microtime(true);
|
||||
|
||||
$this->start = $this->start ?: $start;
|
||||
$this->end = $this->end ?: $end;
|
||||
|
||||
if ($this->start < $start) $this->start = $start;
|
||||
if ($this->end > $end) $this->end = $end;
|
||||
}
|
||||
|
||||
// Fluent API
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (! count($parameters)) return $this->$method;
|
||||
|
||||
$this->$method = $parameters[0];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Return an array representation of the event
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'description' => $this->description,
|
||||
'start' => $this->start,
|
||||
'end' => $this->end,
|
||||
'duration' => $this->duration(),
|
||||
'color' => $this->color,
|
||||
'data' => $this->data
|
||||
];
|
||||
}
|
||||
}
|
72
vendor/itsgoingd/clockwork/Clockwork/Request/Timeline/Timeline.php
vendored
Normal file
72
vendor/itsgoingd/clockwork/Clockwork/Request/Timeline/Timeline.php
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php namespace Clockwork\Request\Timeline;
|
||||
|
||||
// Data structure representing collection of time-based events
|
||||
class Timeline
|
||||
{
|
||||
// Timeline events
|
||||
public $events = [];
|
||||
|
||||
// Create a new timeline, optionally with existing events
|
||||
public function __construct($events = [])
|
||||
{
|
||||
foreach ($events as $event) {
|
||||
$this->create($event['description'], $event);
|
||||
}
|
||||
}
|
||||
|
||||
// Find or create a new event, takes description and optional data - name, start, end, duration, color, data
|
||||
public function event($description, $data = [])
|
||||
{
|
||||
$name = isset($data['name']) ? $data['name'] : $description;
|
||||
|
||||
if ($event = $this->find($name)) return $event;
|
||||
|
||||
return $this->create($description, $data);
|
||||
}
|
||||
|
||||
// Create a new event, takes description and optional data - name, start, end, duration, color, data
|
||||
public function create($description, $data = [])
|
||||
{
|
||||
return $this->events[] = new Event($description, $data);
|
||||
}
|
||||
|
||||
// Find event by name
|
||||
public function find($name)
|
||||
{
|
||||
foreach ($this->events as $event) {
|
||||
if ($event->name == $name) return $event;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge another timeline instance into the current timeline
|
||||
public function merge(Timeline $timeline)
|
||||
{
|
||||
$this->events = array_merge($this->events, $timeline->events);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Finalize timeline, ends all events, sorts them and returns as an array
|
||||
public function finalize($start = null, $end = null)
|
||||
{
|
||||
foreach ($this->events as $event) {
|
||||
$event->finalize($start, $end);
|
||||
}
|
||||
|
||||
$this->sort();
|
||||
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
// Sort the timeline events by start time
|
||||
public function sort()
|
||||
{
|
||||
usort($this->events, function ($a, $b) { return $a->start * 1000 - $b->start * 1000; });
|
||||
}
|
||||
|
||||
// Return events as an array
|
||||
public function toArray()
|
||||
{
|
||||
return array_map(function ($event) { return $event->toArray(); }, $this->events);
|
||||
}
|
||||
}
|
52
vendor/itsgoingd/clockwork/Clockwork/Request/UserData.php
vendored
Normal file
52
vendor/itsgoingd/clockwork/Clockwork/Request/UserData.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php namespace Clockwork\Request;
|
||||
|
||||
// Data structure representing custom user data
|
||||
class UserData
|
||||
{
|
||||
// Data items
|
||||
protected $data = [];
|
||||
|
||||
// Data title
|
||||
protected $title;
|
||||
|
||||
// Add generic user data
|
||||
public function data(array $data, $key = null)
|
||||
{
|
||||
if ($key !== null) {
|
||||
return $this->data[$key] = new UserDataItem($data);
|
||||
}
|
||||
|
||||
return $this->data[] = new UserDataItem($data);
|
||||
}
|
||||
|
||||
// Add user data shown as counters
|
||||
public function counters(array $data)
|
||||
{
|
||||
return $this->data($data)
|
||||
->showAs('counters');
|
||||
}
|
||||
|
||||
// Add user data shown as table
|
||||
public function table($title, array $data)
|
||||
{
|
||||
return $this->data($data)
|
||||
->showAs('table')
|
||||
->title($title);
|
||||
}
|
||||
|
||||
// Set data title
|
||||
public function title($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Transform data and all contents to a serializable array with metadata
|
||||
public function toArray()
|
||||
{
|
||||
return array_merge(
|
||||
array_map(function ($data) { return $data->toArray(); }, $this->data),
|
||||
[ '__meta' => array_filter([ 'title' => $this->title ]) ]
|
||||
);
|
||||
}
|
||||
}
|
55
vendor/itsgoingd/clockwork/Clockwork/Request/UserDataItem.php
vendored
Normal file
55
vendor/itsgoingd/clockwork/Clockwork/Request/UserDataItem.php
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php namespace Clockwork\Request;
|
||||
|
||||
// Data structure representing custom user data item (shown as counters or table)
|
||||
class UserDataItem
|
||||
{
|
||||
// Data contents (labels and values or table rows)
|
||||
protected $data;
|
||||
|
||||
// Describes how the data should be presented ("counters" or "table")
|
||||
protected $showAs;
|
||||
|
||||
// Data title (shown as table title in the official app)
|
||||
protected $title;
|
||||
|
||||
// Map of human-readable labels for the data contents
|
||||
protected $labels;
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
// Set how the item should be presented ("counters" or "table")
|
||||
public function showAs($showAs)
|
||||
{
|
||||
$this->showAs = $showAs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Set data title (shown as table title in the official app)
|
||||
public function title($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Set a map of human-readable labels for the data contents
|
||||
public function labels($labels)
|
||||
{
|
||||
$this->labels = $labels;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Transform contents to a serializable array with metadata
|
||||
public function toArray()
|
||||
{
|
||||
return array_merge($this->data, [
|
||||
'__meta' => array_filter([
|
||||
'showAs' => $this->showAs,
|
||||
'title' => $this->title,
|
||||
'labels' => $this->labels
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
331
vendor/itsgoingd/clockwork/Clockwork/Storage/FileStorage.php
vendored
Normal file
331
vendor/itsgoingd/clockwork/Clockwork/Storage/FileStorage.php
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php namespace Clockwork\Storage;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Storage\Storage;
|
||||
|
||||
// File based storage for requests
|
||||
class FileStorage extends Storage
|
||||
{
|
||||
// Path where files are stored
|
||||
protected $path;
|
||||
|
||||
// Metadata expiration time in minutes
|
||||
protected $expiration;
|
||||
|
||||
// Compress the files using gzip
|
||||
protected $compress;
|
||||
|
||||
// Metadata cleanup chance
|
||||
protected $cleanupChance = 100;
|
||||
|
||||
// Index file handle
|
||||
protected $indexHandle;
|
||||
|
||||
// Return new storage, takes path where to store files as argument, throws exception if path is not writable
|
||||
public function __construct($path, $dirPermissions = 0700, $expiration = null, $compress = false)
|
||||
{
|
||||
if (! file_exists($path)) {
|
||||
// directory doesn't exist, try to create one
|
||||
if (! @mkdir($path, $dirPermissions, true)) {
|
||||
throw new \Exception("Directory \"{$path}\" does not exist.");
|
||||
}
|
||||
|
||||
// create default .gitignore, to ignore stored json files
|
||||
file_put_contents("{$path}/.gitignore", "*.json\n*.json.gz\nindex\n");
|
||||
} elseif (! is_writable($path)) {
|
||||
throw new \Exception("Path \"{$path}\" is not writable.");
|
||||
}
|
||||
|
||||
if (! file_exists($indexFile = "{$path}/index")) {
|
||||
file_put_contents($indexFile, '');
|
||||
} elseif (! is_writable($indexFile)) {
|
||||
throw new \Exception("Path \"{$indexFile}\" is not writable.");
|
||||
}
|
||||
|
||||
$this->path = $path;
|
||||
$this->expiration = $expiration === null ? 60 * 24 * 7 : $expiration;
|
||||
$this->compress = $compress;
|
||||
}
|
||||
|
||||
// Returns all requests
|
||||
public function all(Search $search = null)
|
||||
{
|
||||
return $this->loadRequests($this->searchIndexForward($search));
|
||||
}
|
||||
|
||||
// Return a single request by id
|
||||
public function find($id)
|
||||
{
|
||||
return $this->loadRequest($id);
|
||||
}
|
||||
|
||||
// Return the latest request
|
||||
public function latest(Search $search = null)
|
||||
{
|
||||
$requests = $this->loadRequests($this->searchIndexBackward($search, null, 1));
|
||||
return reset($requests);
|
||||
}
|
||||
|
||||
// Return requests received before specified id, optionally limited to specified count
|
||||
public function previous($id, $count = null, Search $search = null)
|
||||
{
|
||||
return $this->loadRequests($this->searchIndexBackward($search, $id, $count));
|
||||
}
|
||||
|
||||
// Return requests received after specified id, optionally limited to specified count
|
||||
public function next($id, $count = null, Search $search = null)
|
||||
{
|
||||
return $this->loadRequests($this->searchIndexForward($search, $id, $count));
|
||||
}
|
||||
|
||||
// Store request, requests are stored in JSON representation in files named <request id>.json in storage path
|
||||
public function store(Request $request, $skipIndex = false)
|
||||
{
|
||||
$path = "{$this->path}/{$request->id}.json";
|
||||
$data = @json_encode($request->toArray(), \JSON_PARTIAL_OUTPUT_ON_ERROR);
|
||||
|
||||
$this->compress
|
||||
? file_put_contents("{$path}.gz", gzcompress($data))
|
||||
: file_put_contents($path, $data);
|
||||
|
||||
if (! $skipIndex) $this->updateIndex($request);
|
||||
|
||||
$this->cleanup();
|
||||
}
|
||||
|
||||
// Update existing request
|
||||
public function update(Request $request)
|
||||
{
|
||||
return $this->store($request, true);
|
||||
}
|
||||
|
||||
// Cleanup old requests
|
||||
public function cleanup($force = false)
|
||||
{
|
||||
if ($this->expiration === false || (! $force && rand(1, $this->cleanupChance) != 1)) return;
|
||||
|
||||
$this->openIndex('start', true, true); // reopen index with lock
|
||||
|
||||
$expirationTime = time() - ($this->expiration * 60);
|
||||
|
||||
$old = $this->searchIndexForward(
|
||||
new Search([ 'received' => [ '<' . date('c', $expirationTime) ] ], [ 'stopOnFirstMismatch' => true ])
|
||||
);
|
||||
|
||||
if (! count($old)) return $this->closeIndex(true);
|
||||
|
||||
$this->readPreviousIndex();
|
||||
$this->trimIndex();
|
||||
$this->closeIndex(true); // explicitly close index to unlock asap
|
||||
|
||||
foreach ($old as $id) {
|
||||
$path = "{$this->path}/{$id}.json";
|
||||
@unlink($this->compress ? "{$path}.gz" : $path);
|
||||
}
|
||||
}
|
||||
|
||||
// Load a single request by id from filesystem
|
||||
protected function loadRequest($id)
|
||||
{
|
||||
$path = "{$this->path}/{$id}.json";
|
||||
|
||||
if (! is_readable($this->compress ? "{$path}.gz" : $path)) return;
|
||||
|
||||
$data = file_get_contents($this->compress ? "{$path}.gz" : $path);
|
||||
|
||||
return new Request(json_decode($this->compress ? gzuncompress($data) : $data, true));
|
||||
}
|
||||
|
||||
// Load multiple requests by ids from filesystem
|
||||
protected function loadRequests($ids)
|
||||
{
|
||||
return array_filter(array_map(function ($id) { return $this->loadRequest($id); }, $ids));
|
||||
}
|
||||
|
||||
// Search index backward from specified ID or last record, with optional results count limit
|
||||
protected function searchIndexBackward(Search $search = null, $id = null, $count = null)
|
||||
{
|
||||
return $this->searchIndex('previous', $search, $id, $count);
|
||||
}
|
||||
|
||||
// Search index forward from specified ID or last record, with optional results count limit
|
||||
protected function searchIndexForward(Search $search = null, $id = null, $count = null)
|
||||
{
|
||||
return $this->searchIndex('next', $search, $id, $count);
|
||||
}
|
||||
|
||||
// Search index in specified direction from specified ID or last record, with optional results count limit
|
||||
protected function searchIndex($direction, Search $search = null, $id = null, $count = null)
|
||||
{
|
||||
$this->openIndex($direction == 'previous' ? 'end' : 'start', false, true);
|
||||
|
||||
if ($id) {
|
||||
while ($request = $this->readIndex($direction)) { if ($request->id == $id) break; }
|
||||
}
|
||||
|
||||
$found = [];
|
||||
|
||||
while ($request = $this->readIndex($direction)) {
|
||||
if (! $search || $search->matches($request)) {
|
||||
$found[] = $request->id;
|
||||
} elseif ($search->stopOnFirstMismatch) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($count && count($found) == $count) break;
|
||||
}
|
||||
|
||||
return $direction == 'next' ? $found : array_reverse($found);
|
||||
}
|
||||
|
||||
// Open index file, optionally lock or move file pointer to the end, existing handle will be returned by default
|
||||
protected function openIndex($position = 'start', $lock = false, $force = false)
|
||||
{
|
||||
if ($this->indexHandle) {
|
||||
if (! $force) return;
|
||||
$this->closeIndex();
|
||||
}
|
||||
|
||||
$this->indexHandle = fopen("{$this->path}/index", 'r');
|
||||
|
||||
if ($lock) flock($this->indexHandle, LOCK_EX);
|
||||
if ($position == 'end') fseek($this->indexHandle, 0, SEEK_END);
|
||||
}
|
||||
|
||||
// Close index file, optionally unlock
|
||||
protected function closeIndex($lock = false)
|
||||
{
|
||||
if ($lock) flock($this->indexHandle, LOCK_UN);
|
||||
fclose($this->indexHandle);
|
||||
|
||||
$this->indexHandle = null;
|
||||
}
|
||||
|
||||
// Read a line from index in the specified direction (next or previous)
|
||||
protected function readIndex($direction)
|
||||
{
|
||||
return $direction == 'next' ? $this->readNextIndex() : $this->readPreviousIndex();
|
||||
}
|
||||
|
||||
// Read previous line from index
|
||||
protected function readPreviousIndex()
|
||||
{
|
||||
$position = ftell($this->indexHandle) - 1;
|
||||
|
||||
if ($position <= 0) return;
|
||||
|
||||
$line = '';
|
||||
|
||||
// reads 1024B chunks of the file backwards from the current position, until a newline is found or we reach the top
|
||||
while ($position > 0) {
|
||||
// find next position to read from, make sure we don't read beyond file boundary
|
||||
$position -= $chunkSize = min($position, 1024);
|
||||
|
||||
// read the chunk from the position
|
||||
fseek($this->indexHandle, $position);
|
||||
$chunk = fread($this->indexHandle, $chunkSize);
|
||||
|
||||
// if a newline is found, append only the part after the last newline, otherwise we can append the whole chunk
|
||||
$line = ($newline = strrpos($chunk, "\n")) === false
|
||||
? $chunk . $line : substr($chunk, $newline + 1) . $line;
|
||||
|
||||
// if a newline was found, fix the position so we read from that newline next time
|
||||
if ($newline !== false) $position += $newline + 1;
|
||||
|
||||
// move file pointer to the correct position (revert fread, apply newline fix)
|
||||
fseek($this->indexHandle, $position);
|
||||
|
||||
// if we reached a newline and put together a non-empty line we are done
|
||||
if ($newline !== false) break;
|
||||
}
|
||||
|
||||
return $this->makeRequestFromIndex(str_getcsv($line));
|
||||
}
|
||||
|
||||
// Read next line from index
|
||||
protected function readNextIndex()
|
||||
{
|
||||
if (feof($this->indexHandle)) return;
|
||||
|
||||
// File pointer is always at the start of the line, call extra fgets to skip current line
|
||||
fgets($this->indexHandle);
|
||||
$line = fgets($this->indexHandle);
|
||||
|
||||
// Check if we read an empty line or reached the end of file
|
||||
if ($line === false) return;
|
||||
|
||||
// Reset the file pointer to the start of the read line
|
||||
fseek($this->indexHandle, ftell($this->indexHandle) - strlen($line));
|
||||
|
||||
return $this->makeRequestFromIndex(str_getcsv($line));
|
||||
}
|
||||
|
||||
// Trim index file from beginning to current position (including)
|
||||
protected function trimIndex()
|
||||
{
|
||||
// File pointer is always at the start of the line, call extra fgets to skip current line
|
||||
fgets($this->indexHandle);
|
||||
|
||||
// Read the rest of the index file
|
||||
$trimmedLength = filesize("{$this->path}/index") - ftell($this->indexHandle);
|
||||
$trimmed = $trimmedLength > 0 ? fread($this->indexHandle, $trimmedLength) : '';
|
||||
|
||||
// Rewrite the index file with a trimmed version
|
||||
file_put_contents("{$this->path}/index", $trimmed);
|
||||
}
|
||||
|
||||
// Create an incomplete request from index data
|
||||
protected function makeRequestFromIndex($record)
|
||||
{
|
||||
$type = isset($record[7]) ? $record[7] : 'response';
|
||||
|
||||
if ($type == 'command') {
|
||||
$nameField = 'commandName';
|
||||
} elseif ($type == 'queue-job') {
|
||||
$nameField = 'jobName';
|
||||
} elseif ($type == 'test') {
|
||||
$nameField = 'testName';
|
||||
} else {
|
||||
$nameField = 'uri';
|
||||
}
|
||||
|
||||
return new Request(array_combine(
|
||||
[ 'id', 'time', 'method', $nameField, 'controller', 'responseStatus', 'responseDuration', 'type' ],
|
||||
array_slice($record, 0, 8) + [ null, null, null, null, null, null, null, 'response' ]
|
||||
));
|
||||
}
|
||||
|
||||
// Update index with a new request
|
||||
protected function updateIndex(Request $request)
|
||||
{
|
||||
$handle = fopen("{$this->path}/index", 'a');
|
||||
|
||||
if (! $handle) return;
|
||||
|
||||
if (! flock($handle, LOCK_EX)) return fclose($handle);
|
||||
|
||||
if ($request->type == 'command') {
|
||||
$nameField = 'commandName';
|
||||
} elseif ($request->type == 'queue-job') {
|
||||
$nameField = 'jobName';
|
||||
} elseif ($request->type == 'test') {
|
||||
$nameField = 'testName';
|
||||
} else {
|
||||
$nameField = 'uri';
|
||||
}
|
||||
|
||||
fputcsv($handle, [
|
||||
$request->id,
|
||||
$request->time,
|
||||
$request->method,
|
||||
$request->$nameField,
|
||||
$request->controller,
|
||||
$request->responseStatus,
|
||||
$request->getResponseDuration(),
|
||||
$request->type
|
||||
]);
|
||||
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
166
vendor/itsgoingd/clockwork/Clockwork/Storage/Search.php
vendored
Normal file
166
vendor/itsgoingd/clockwork/Clockwork/Storage/Search.php
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php namespace Clockwork\Storage;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Request\RequestType;
|
||||
|
||||
// Rules for searching requests
|
||||
class Search
|
||||
{
|
||||
// Search parameters
|
||||
public $uri = [];
|
||||
public $controller = [];
|
||||
public $method = [];
|
||||
public $status = [];
|
||||
public $time = [];
|
||||
public $received = [];
|
||||
public $name = [];
|
||||
public $type = [];
|
||||
|
||||
// Whether to stop search on the first not matching request
|
||||
public $stopOnFirstMismatch = false;
|
||||
|
||||
// Create a new instance, takes search parameters and additional options
|
||||
public function __construct($search = [], $options = [])
|
||||
{
|
||||
foreach ([ 'uri', 'controller', 'method', 'status', 'time', 'received', 'name', 'type' ] as $condition) {
|
||||
$this->$condition = isset($search[$condition]) ? $search[$condition] : [];
|
||||
}
|
||||
|
||||
foreach ([ 'stopOnFirstMismatch' ] as $option) {
|
||||
$this->$option = isset($options[$option]) ? $options[$option] : $this->$condition;
|
||||
}
|
||||
|
||||
$this->method = array_map('strtolower', $this->method);
|
||||
}
|
||||
|
||||
// Create a new instance from request input
|
||||
public static function fromRequest($data = [])
|
||||
{
|
||||
return new static($data);
|
||||
}
|
||||
|
||||
// Check whether the request matches current search parameters
|
||||
public function matches(Request $request)
|
||||
{
|
||||
if ($request->type == RequestType::COMMAND) {
|
||||
return $this->matchesCommand($request);
|
||||
} elseif ($request->type == RequestType::QUEUE_JOB) {
|
||||
return $this->matchesQueueJob($request);
|
||||
} elseif ($request->type == RequestType::TEST) {
|
||||
return $this->matchesTest($request);
|
||||
} else {
|
||||
return $this->matchesRequest($request);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether a request type request matches
|
||||
protected function matchesRequest(Request $request)
|
||||
{
|
||||
return $this->matchesString($this->type, RequestType::REQUEST)
|
||||
&& $this->matchesString($this->uri, $request->uri)
|
||||
&& $this->matchesString($this->controller, $request->controller)
|
||||
&& $this->matchesExact($this->method, strtolower($request->method))
|
||||
&& $this->matchesNumber($this->status, $request->responseStatus)
|
||||
&& $this->matchesNumber($this->time, $request->responseDuration)
|
||||
&& $this->matchesDate($this->received, $request->time);
|
||||
}
|
||||
|
||||
// Check whether a command type request matches
|
||||
protected function matchesCommand(Request $request)
|
||||
{
|
||||
return $this->matchesString($this->type, RequestType::COMMAND)
|
||||
&& $this->matchesString($this->name, $request->commandName)
|
||||
&& $this->matchesNumber($this->status, $request->commandExitCode)
|
||||
&& $this->matchesNumber($this->time, $request->responseDuration)
|
||||
&& $this->matchesDate($this->received, $request->time);
|
||||
}
|
||||
|
||||
// Check whether a queue-job type request matches
|
||||
protected function matchesQueueJob(Request $request)
|
||||
{
|
||||
return $this->matchesString($this->type, RequestType::QUEUE_JOB)
|
||||
&& $this->matchesString($this->name, $request->jobName)
|
||||
&& $this->matchesString($this->status, $request->jobStatus)
|
||||
&& $this->matchesNumber($this->time, $request->responseDuration)
|
||||
&& $this->matchesDate($this->received, $request->time);
|
||||
}
|
||||
|
||||
// Check whether a test type request matches
|
||||
protected function matchesTest(Request $request)
|
||||
{
|
||||
return $this->matchesString($this->type, RequestType::TEST)
|
||||
&& $this->matchesString($this->name, $request->testName)
|
||||
&& $this->matchesString($this->status, $request->testStatus)
|
||||
&& $this->matchesNumber($this->time, $request->responseDuration)
|
||||
&& $this->matchesDate($this->received, $request->time);
|
||||
}
|
||||
|
||||
// Check if there are no search parameters specified
|
||||
public function isEmpty()
|
||||
{
|
||||
return ! count($this->uri) && ! count($this->controller) && ! count($this->method) && ! count($this->status)
|
||||
&& ! count($this->time) && ! count($this->received) && ! count($this->name) && ! count($this->type);
|
||||
}
|
||||
|
||||
// Check if there are some search parameters specified
|
||||
public function isNotEmpty()
|
||||
{
|
||||
return ! $this->isEmpty();
|
||||
}
|
||||
|
||||
// Check if the value matches date type search parameter
|
||||
protected function matchesDate($inputs, $value)
|
||||
{
|
||||
if (! count($inputs)) return true;
|
||||
|
||||
foreach ($inputs as $input) {
|
||||
if (preg_match('/^<(.+)$/', $input, $match)) {
|
||||
if ($value < strtotime($match[1])) return true;
|
||||
} elseif (preg_match('/^>(.+)$/', $input, $match)) {
|
||||
if ($value > strtotime($match[1])) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the value matches exact type search parameter
|
||||
protected function matchesExact($inputs, $value)
|
||||
{
|
||||
if (! count($inputs)) return true;
|
||||
|
||||
return in_array($value, $inputs);
|
||||
}
|
||||
|
||||
// Check if the value matches number type search parameter
|
||||
protected function matchesNumber($inputs, $value)
|
||||
{
|
||||
if (! count($inputs)) return true;
|
||||
|
||||
foreach ($inputs as $input) {
|
||||
if (preg_match('/^<(\d+(?:\.\d+)?)$/', $input, $match)) {
|
||||
if ($value < $match[1]) return true;
|
||||
} elseif (preg_match('/^>(\d+(?:\.\d+)?)$/', $input, $match)) {
|
||||
if ($value > $match[1]) return true;
|
||||
} elseif (preg_match('/^(\d+(?:\.\d+)?)-(\d+(?:\.\d+)?)$/', $input, $match)) {
|
||||
if ($match[1] < $value && $value < $match[2]) return true;
|
||||
} else {
|
||||
if ($value == $input) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the value matches string type search parameter
|
||||
protected function matchesString($inputs, $value)
|
||||
{
|
||||
if (! count($inputs)) return true;
|
||||
|
||||
foreach ($inputs as $input) {
|
||||
if (strpos($value, $input) !== false) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
164
vendor/itsgoingd/clockwork/Clockwork/Storage/SqlSearch.php
vendored
Normal file
164
vendor/itsgoingd/clockwork/Clockwork/Storage/SqlSearch.php
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php namespace Clockwork\Storage;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use PDO;
|
||||
|
||||
// Rules for searching requests using SQL storage, builds the SQL query conditions
|
||||
class SqlSearch extends Search
|
||||
{
|
||||
// Generated SQL query and bindings
|
||||
public $query;
|
||||
public $bindings;
|
||||
|
||||
// Internal representation of the SQL where conditions
|
||||
protected $conditions;
|
||||
|
||||
// PDO instance
|
||||
protected $pdo;
|
||||
|
||||
// Create a new instance, takes search parameters
|
||||
public function __construct($search = [], PDO $pdo = null)
|
||||
{
|
||||
parent::__construct($search);
|
||||
|
||||
$this->pdo = $pdo;
|
||||
|
||||
list($this->conditions, $this->bindings) = $this->resolveConditions();
|
||||
|
||||
$this->buildQuery();
|
||||
}
|
||||
|
||||
// Creates a new instance from a base Search class instance
|
||||
public static function fromBase(Search $search = null, PDO $pdo = null)
|
||||
{
|
||||
return new static((array) $search, $pdo);
|
||||
}
|
||||
|
||||
// Add an additional where condition, takes the SQL condition and array of bindings
|
||||
public function addCondition($condition, $bindings = [])
|
||||
{
|
||||
$this->conditions = array_merge([ $condition ], $this->conditions);
|
||||
$this->bindings = array_merge($bindings, $this->bindings);
|
||||
$this->buildQuery();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Resolve SQL conditions and bindings based on the search parameters
|
||||
protected function resolveConditions()
|
||||
{
|
||||
if ($this->isEmpty()) return [ [], [] ];
|
||||
|
||||
$conditions = array_filter([
|
||||
$this->resolveStringCondition([ 'type' ], $this->type),
|
||||
$this->resolveStringCondition([ 'uri', 'commandName', 'jobName', 'testName' ], array_merge($this->uri, $this->name)),
|
||||
$this->resolveStringCondition([ 'controller' ], $this->controller),
|
||||
$this->resolveExactCondition([ 'method' ], $this->method),
|
||||
$this->resolveNumberCondition([ 'responseStatus', 'commandExitCode', 'jobStatus', 'testStatus' ], $this->status),
|
||||
$this->resolveNumberCondition([ 'responseDuration' ], $this->time),
|
||||
$this->resolveDateCondition([ 'time' ], $this->received)
|
||||
]);
|
||||
|
||||
$sql = array_map(function ($condition) { return $condition[0]; }, $conditions);
|
||||
$bindings = array_reduce($conditions, function ($bindings, $condition) {
|
||||
return array_merge($bindings, $condition[1]);
|
||||
}, []);
|
||||
|
||||
return [ $sql, $bindings ];
|
||||
}
|
||||
|
||||
// Resolve a date type condition and bindings
|
||||
protected function resolveDateCondition($fields, $inputs)
|
||||
{
|
||||
if (! count($inputs)) return null;
|
||||
|
||||
$bindings = [];
|
||||
$conditions = implode(' OR ', array_map(function ($field) use ($inputs, &$bindings) {
|
||||
return implode(' OR ', array_map(function ($input, $index) use ($field, &$bindings) {
|
||||
if (preg_match('/^<(.+)$/', $input, $match)) {
|
||||
$bindings["{$field}{$index}"] = $match[1];
|
||||
return $this->quote($field) . " < :{$field}{$index}";
|
||||
} elseif (preg_match('/^>(.+)$/', $input, $match)) {
|
||||
$bindings["{$field}{$index}"] = $match[1];
|
||||
return $this->quote($field). " > :{$field}{$index}";
|
||||
}
|
||||
}, $inputs, array_keys($inputs)));
|
||||
}, $fields));
|
||||
|
||||
return [ "({$conditions})", $bindings ];
|
||||
}
|
||||
|
||||
// Resolve an exact type condition and bindings
|
||||
protected function resolveExactCondition($fields, $inputs)
|
||||
{
|
||||
if (! count($inputs)) return null;
|
||||
|
||||
$bindings = [];
|
||||
$values = implode(' OR ', array_map(function ($field) use ($inputs, &$bindings) {
|
||||
return implode(', ', array_map(function ($input, $index) use ($field, &$bindings) {
|
||||
$bindings["{$field}{$index}"] = $input;
|
||||
return ":{$field}{$index}";
|
||||
}, $inputs, array_keys($inputs)));
|
||||
}, $fields));
|
||||
|
||||
return [ $this->quote($field) . " IN ({$values})", $bindings ];
|
||||
}
|
||||
|
||||
// Resolve a number type condition and bindings
|
||||
protected function resolveNumberCondition($fields, $inputs)
|
||||
{
|
||||
if (! count($inputs)) return null;
|
||||
|
||||
$bindings = [];
|
||||
$conditions = implode(' OR ', array_map(function ($field) use ($inputs, &$bindings) {
|
||||
return implode(' OR ', array_map(function ($input, $index) use ($field, &$bindings) {
|
||||
if (preg_match('/^<(\d+(?:\.\d+)?)$/', $input, $match)) {
|
||||
$bindings["{$field}{$index}"] = $match[1];
|
||||
return $this->quote($field) . " < :{$field}{$index}";
|
||||
} elseif (preg_match('/^>(\d+(?:\.\d+)?)$/', $input, $match)) {
|
||||
$bindings["{$field}{$index}"] = $match[1];
|
||||
return $this->quote($field) . " > :{$field}{$index}";
|
||||
} elseif (preg_match('/^(\d+(?:\.\d+)?)-(\d+(?:\.\d+)?)$/', $input, $match)) {
|
||||
$bindings["{$field}{$index}from"] = $match[1];
|
||||
$bindings["{$field}{$index}to"] = $match[2];
|
||||
$quotedField = $this->quote($field);
|
||||
return "({$quotedField} > :{$field}{$index}from AND {$quotedField} < :{$field}{$index}to)";
|
||||
} else {
|
||||
$bindings["{$field}{$index}"] = $input;
|
||||
return $this->quote($field) . " = :{$field}{$index}";
|
||||
}
|
||||
}, $inputs, array_keys($inputs)));
|
||||
}, $fields));
|
||||
|
||||
return [ "({$conditions})", $bindings ];
|
||||
}
|
||||
|
||||
// Resolve a string type condition and bindings
|
||||
protected function resolveStringCondition($fields, $inputs)
|
||||
{
|
||||
if (! count($inputs)) return null;
|
||||
|
||||
$bindings = [];
|
||||
$conditions = implode(' OR ', array_map(function ($field) use ($inputs, &$bindings) {
|
||||
return implode(' OR ', array_map(function ($input, $index) use ($field, &$bindings) {
|
||||
$bindings["{$field}{$index}"] = $input;
|
||||
return $this->quote($field) . " LIKE :{$field}{$index}";
|
||||
}, $inputs, array_keys($inputs)));
|
||||
}, $fields));
|
||||
|
||||
return [ "({$conditions})", $bindings ];
|
||||
}
|
||||
|
||||
// Build the where part of the SQL query
|
||||
protected function buildQuery()
|
||||
{
|
||||
$this->query = count($this->conditions) ? 'WHERE ' . implode(' AND ', $this->conditions) : '';
|
||||
}
|
||||
|
||||
// Quotes SQL identifier name properly for the current database
|
||||
protected function quote($identifier)
|
||||
{
|
||||
return $this->pdo && $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql' ? "`{$identifier}`" : "\"{$identifier}\"";
|
||||
}
|
||||
}
|
296
vendor/itsgoingd/clockwork/Clockwork/Storage/SqlStorage.php
vendored
Normal file
296
vendor/itsgoingd/clockwork/Clockwork/Storage/SqlStorage.php
vendored
Normal file
@@ -0,0 +1,296 @@
|
||||
<?php namespace Clockwork\Storage;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
use PDO;
|
||||
|
||||
// SQL storage for requests using PDO
|
||||
class SqlStorage extends Storage
|
||||
{
|
||||
// PDO instance
|
||||
protected $pdo;
|
||||
|
||||
// Name of the table with Clockwork requests metadata
|
||||
protected $table;
|
||||
|
||||
// Metadata expiration time in minutes
|
||||
protected $expiration;
|
||||
|
||||
// Schema for the Clockwork requests table
|
||||
protected $fields = [
|
||||
'id' => 'VARCHAR(100) PRIMARY KEY',
|
||||
'version' => 'INTEGER',
|
||||
'type' => 'VARCHAR(100) NULL',
|
||||
'time' => 'DOUBLE PRECISION NULL',
|
||||
'method' => 'VARCHAR(10) NULL',
|
||||
'url' => 'TEXT NULL',
|
||||
'uri' => 'TEXT NULL',
|
||||
'headers' => 'TEXT NULL',
|
||||
'controller' => 'VARCHAR(250) NULL',
|
||||
'getData' => 'TEXT NULL',
|
||||
'postData' => 'TEXT NULL',
|
||||
'requestData' => 'TEXT NULL',
|
||||
'sessionData' => 'TEXT NULL',
|
||||
'authenticatedUser' => 'TEXT NULL',
|
||||
'cookies' => 'TEXT NULL',
|
||||
'responseTime' => 'DOUBLE PRECISION NULL',
|
||||
'responseStatus' => 'INTEGER NULL',
|
||||
'responseDuration' => 'DOUBLE PRECISION NULL',
|
||||
'memoryUsage' => 'DOUBLE PRECISION NULL',
|
||||
'middleware' => 'TEXT NULL',
|
||||
'databaseQueries' => 'TEXT NULL',
|
||||
'databaseQueriesCount' => 'INTEGER NULL',
|
||||
'databaseSlowQueries' => 'INTEGER NULL',
|
||||
'databaseSelects' => 'INTEGER NULL',
|
||||
'databaseInserts' => 'INTEGER NULL',
|
||||
'databaseUpdates' => 'INTEGER NULL',
|
||||
'databaseDeletes' => 'INTEGER NULL',
|
||||
'databaseOthers' => 'INTEGER NULL',
|
||||
'databaseDuration' => 'DOUBLE PRECISION NULL',
|
||||
'cacheQueries' => 'TEXT NULL',
|
||||
'cacheReads' => 'INTEGER NULL',
|
||||
'cacheHits' => 'INTEGER NULL',
|
||||
'cacheWrites' => 'INTEGER NULL',
|
||||
'cacheDeletes' => 'INTEGER NULL',
|
||||
'cacheTime' => 'DOUBLE PRECISION NULL',
|
||||
'modelsActions' => 'TEXT NULL',
|
||||
'modelsRetrieved' => 'TEXT NULL',
|
||||
'modelsCreated' => 'TEXT NULL',
|
||||
'modelsUpdated' => 'TEXT NULL',
|
||||
'modelsDeleted' => 'TEXT NULL',
|
||||
'redisCommands' => 'TEXT NULL',
|
||||
'queueJobs' => 'TEXT NULL',
|
||||
'timelineData' => 'TEXT NULL',
|
||||
'log' => 'TEXT NULL',
|
||||
'events' => 'TEXT NULL',
|
||||
'routes' => 'TEXT NULL',
|
||||
'notifications' => 'TEXT NULL',
|
||||
'emailsData' => 'TEXT NULL',
|
||||
'viewsData' => 'TEXT NULL',
|
||||
'userData' => 'TEXT NULL',
|
||||
'subrequests' => 'TEXT NULL',
|
||||
'xdebug' => 'TEXT NULL',
|
||||
'commandName' => 'TEXT NULL',
|
||||
'commandArguments' => 'TEXT NULL',
|
||||
'commandArgumentsDefaults' => 'TEXT NULL',
|
||||
'commandOptions' => 'TEXT NULL',
|
||||
'commandOptionsDefaults' => 'TEXT NULL',
|
||||
'commandExitCode' => 'INTEGER NULL',
|
||||
'commandOutput' => 'TEXT NULL',
|
||||
'jobName' => 'TEXT NULL',
|
||||
'jobDescription' => 'TEXT NULL',
|
||||
'jobStatus' => 'TEXT NULL',
|
||||
'jobPayload' => 'TEXT NULL',
|
||||
'jobQueue' => 'TEXT NULL',
|
||||
'jobConnection' => 'TEXT NULL',
|
||||
'jobOptions' => 'TEXT NULL',
|
||||
'testName' => 'TEXT NULL',
|
||||
'testStatus' => 'TEXT NULL',
|
||||
'testStatusMessage' => 'TEXT NULL',
|
||||
'testAsserts' => 'TEXT NULL',
|
||||
'clientMetrics' => 'TEXT NULL',
|
||||
'webVitals' => 'TEXT NULL',
|
||||
'parent' => 'TEXT NULL',
|
||||
'updateToken' => 'VARCHAR(100) NULL'
|
||||
];
|
||||
|
||||
// List of Request keys that need to be serialized before they can be stored in database
|
||||
protected $needsSerialization = [
|
||||
'headers', 'getData', 'postData', 'requestData', 'sessionData', 'authenticatedUser', 'cookies', 'middleware',
|
||||
'databaseQueries', 'cacheQueries', 'modelsActions', 'modelsRetrieved', 'modelsCreated', 'modelsUpdated',
|
||||
'modelsDeleted', 'redisCommands', 'queueJobs', 'timelineData', 'log', 'events', 'routes', 'notifications',
|
||||
'emailsData', 'viewsData', 'userData', 'subrequests', 'xdebug', 'commandArguments', 'commandArgumentsDefaults',
|
||||
'commandOptions', 'commandOptionsDefaults', 'jobPayload', 'jobOptions', 'testAsserts', 'parent',
|
||||
'clientMetrics', 'webVitals'
|
||||
];
|
||||
|
||||
// Return a new storage, takes PDO object or DSN and optionally a table name and database credentials as arguments
|
||||
public function __construct($dsn, $table = 'clockwork', $username = null, $password = null, $expiration = null)
|
||||
{
|
||||
$this->pdo = $dsn instanceof PDO ? $dsn : new PDO($dsn, $username, $password);
|
||||
$this->table = $table;
|
||||
$this->expiration = $expiration === null ? 60 * 24 * 7 : $expiration;
|
||||
}
|
||||
|
||||
// Returns all requests
|
||||
public function all(Search $search = null)
|
||||
{
|
||||
$fields = implode(', ', array_map(function ($field) { return $this->quote($field); }, array_keys($this->fields)));
|
||||
$search = SqlSearch::fromBase($search, $this->pdo);
|
||||
$result = $this->query("SELECT {$fields} FROM {$this->table} {$search->query}", $search->bindings);
|
||||
|
||||
return $this->resultsToRequests($result);
|
||||
}
|
||||
|
||||
// Return a single request by id
|
||||
public function find($id)
|
||||
{
|
||||
$fields = implode(', ', array_map(function ($field) { return $this->quote($field); }, array_keys($this->fields)));
|
||||
$result = $this->query("SELECT {$fields} FROM {$this->table} WHERE id = :id", [ 'id' => $id ]);
|
||||
|
||||
$requests = $this->resultsToRequests($result);
|
||||
return end($requests);
|
||||
}
|
||||
|
||||
// Return the latest request
|
||||
public function latest(Search $search = null)
|
||||
{
|
||||
$fields = implode(', ', array_map(function ($field) { return $this->quote($field); }, array_keys($this->fields)));
|
||||
$search = SqlSearch::fromBase($search, $this->pdo);
|
||||
$result = $this->query(
|
||||
"SELECT {$fields} FROM {$this->table} {$search->query} ORDER BY id DESC LIMIT 1", $search->bindings
|
||||
);
|
||||
|
||||
$requests = $this->resultsToRequests($result);
|
||||
return end($requests);
|
||||
}
|
||||
|
||||
// Return requests received before specified id, optionally limited to specified count
|
||||
public function previous($id, $count = null, Search $search = null)
|
||||
{
|
||||
$count = (int) $count;
|
||||
|
||||
$fields = implode(', ', array_map(function ($field) { return $this->quote($field); }, array_keys($this->fields)));
|
||||
$search = SqlSearch::fromBase($search, $this->pdo)->addCondition('id < :id', [ 'id' => $id ]);
|
||||
$limit = $count ? "LIMIT {$count}" : '';
|
||||
$result = $this->query(
|
||||
"SELECT {$fields} FROM {$this->table} {$search->query} ORDER BY id DESC {$limit}", $search->bindings
|
||||
);
|
||||
|
||||
return array_reverse($this->resultsToRequests($result));
|
||||
}
|
||||
|
||||
// Return requests received after specified id, optionally limited to specified count
|
||||
public function next($id, $count = null, Search $search = null)
|
||||
{
|
||||
$count = (int) $count;
|
||||
|
||||
$fields = implode(', ', array_map(function ($field) { return $this->quote($field); }, array_keys($this->fields)));
|
||||
$search = SqlSearch::fromBase($search, $this->pdo)->addCondition('id > :id', [ 'id' => $id ]);
|
||||
$limit = $count ? "LIMIT {$count}" : '';
|
||||
$result = $this->query(
|
||||
"SELECT {$fields} FROM {$this->table} {$search->query} ORDER BY id ASC {$limit}", $search->bindings
|
||||
);
|
||||
|
||||
return $this->resultsToRequests($result);
|
||||
}
|
||||
|
||||
// Store the request in the database
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->toArray();
|
||||
|
||||
foreach ($this->needsSerialization as $key) {
|
||||
$data[$key] = @json_encode($data[$key], \JSON_PARTIAL_OUTPUT_ON_ERROR);
|
||||
}
|
||||
|
||||
$fields = implode(', ', array_map(function ($field) { return $this->quote($field); }, array_keys($this->fields)));
|
||||
$bindings = implode(', ', array_map(function ($field) { return ":{$field}"; }, array_keys($this->fields)));
|
||||
|
||||
$this->query("INSERT INTO {$this->table} ($fields) VALUES ($bindings)", $data);
|
||||
|
||||
$this->cleanup();
|
||||
}
|
||||
|
||||
// Update an existing request in the database
|
||||
public function update(Request $request)
|
||||
{
|
||||
$data = $request->toArray();
|
||||
|
||||
foreach ($this->needsSerialization as $key) {
|
||||
$data[$key] = @json_encode($data[$key], \JSON_PARTIAL_OUTPUT_ON_ERROR);
|
||||
}
|
||||
|
||||
$values = implode(', ', array_map(function ($field) {
|
||||
return $this->quote($field) . " = :{$field}";
|
||||
}, array_keys($this->fields)));
|
||||
|
||||
$this->query("UPDATE {$this->table} SET {$values} WHERE id = :id", $data);
|
||||
|
||||
$this->cleanup();
|
||||
}
|
||||
|
||||
// Cleanup old requests
|
||||
public function cleanup()
|
||||
{
|
||||
if ($this->expiration === false) return;
|
||||
|
||||
$this->query("DELETE FROM {$this->table} WHERE time < :time", [ 'time' => time() - ($this->expiration * 60) ]);
|
||||
}
|
||||
|
||||
// Create or update the Clockwork metadata table
|
||||
protected function initialize()
|
||||
{
|
||||
// first we get rid of existing table if it exists by renaming it so we won't lose any data
|
||||
try {
|
||||
$table = $this->quote($this->table);
|
||||
$backupTableName = $this->quote("{$this->table}_backup_" . date('Ymd'));
|
||||
$this->pdo->exec("ALTER TABLE {$table} RENAME TO {$backupTableName};");
|
||||
} catch (\PDOException $e) {
|
||||
// this just means the table doesn't yet exist, nothing to do here
|
||||
}
|
||||
|
||||
// create the metadata table
|
||||
$this->pdo->exec($this->buildSchema($table));
|
||||
|
||||
$indexName = $this->quote("{$this->table}_time_index");
|
||||
$this->pdo->exec("CREATE INDEX {$indexName} ON {$table} (". $this->quote('time') .')');
|
||||
}
|
||||
|
||||
// Builds the query to create Clockwork database table
|
||||
protected function buildSchema($table)
|
||||
{
|
||||
$textType = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql' ? 'MEDIUMTEXT' : 'TEXT';
|
||||
|
||||
$columns = implode(', ', array_map(function ($field, $type) use ($textType) {
|
||||
return $this->quote($field) . ' ' . str_replace('TEXT', $textType, $type);
|
||||
}, array_keys($this->fields), array_values($this->fields)));
|
||||
|
||||
return "CREATE TABLE {$table} ({$columns});";
|
||||
}
|
||||
|
||||
// Executes an sql query, lazily initiates the clockwork database schema if it's old or doesn't exist yet, returns
|
||||
// executed statement or false on error
|
||||
protected function query($query, array $bindings = [], $firstTry = true)
|
||||
{
|
||||
try {
|
||||
if ($stmt = $this->pdo->prepare($query)) {
|
||||
if ($stmt->execute($bindings)) return $stmt;
|
||||
throw new \PDOException;
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
$stmt = false;
|
||||
}
|
||||
|
||||
// the query failed to execute, assume it's caused by missing or old schema, try to reinitialize database
|
||||
if (! $stmt && $firstTry) {
|
||||
$this->initialize();
|
||||
return $this->query($query, $bindings, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Quotes SQL identifier name properly for the current database
|
||||
protected function quote($identifier)
|
||||
{
|
||||
return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql' ? "`{$identifier}`" : "\"{$identifier}\"";
|
||||
}
|
||||
|
||||
// Returns array of Requests instances from the executed PDO statement
|
||||
protected function resultsToRequests($stmt)
|
||||
{
|
||||
return array_map(function ($data) {
|
||||
return $this->dataToRequest($data);
|
||||
}, $stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
}
|
||||
|
||||
// Returns a Request instance from a single database record
|
||||
protected function dataToRequest($data)
|
||||
{
|
||||
foreach ($this->needsSerialization as $key) {
|
||||
$data[$key] = json_decode($data[$key], true);
|
||||
}
|
||||
|
||||
return new Request($data);
|
||||
}
|
||||
}
|
12
vendor/itsgoingd/clockwork/Clockwork/Storage/Storage.php
vendored
Normal file
12
vendor/itsgoingd/clockwork/Clockwork/Storage/Storage.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php namespace Clockwork\Storage;
|
||||
|
||||
use Clockwork\Storage\StorageInterface;
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
abstract class Storage implements StorageInterface
|
||||
{
|
||||
// Update existing request
|
||||
public function update(Request $request)
|
||||
{
|
||||
}
|
||||
}
|
31
vendor/itsgoingd/clockwork/Clockwork/Storage/StorageInterface.php
vendored
Normal file
31
vendor/itsgoingd/clockwork/Clockwork/Storage/StorageInterface.php
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php namespace Clockwork\Storage;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
|
||||
// Interface for requests storage implementations
|
||||
interface StorageInterface
|
||||
{
|
||||
// Returns all requests
|
||||
public function all(Search $search = null);
|
||||
|
||||
// Return a single request by id
|
||||
public function find($id);
|
||||
|
||||
// Return the latest request
|
||||
public function latest(Search $search = null);
|
||||
|
||||
// Return requests received before specified id, optionally limited to specified count
|
||||
public function previous($id, $count = null, Search $search = null);
|
||||
|
||||
// Return requests received after specified id, optionally limited to specified count
|
||||
public function next($id, $count = null, Search $search = null);
|
||||
|
||||
// Store request
|
||||
public function store(Request $request);
|
||||
|
||||
// Update existing request
|
||||
public function update(Request $request);
|
||||
|
||||
// Cleanup old requests
|
||||
public function cleanup();
|
||||
}
|
55
vendor/itsgoingd/clockwork/Clockwork/Storage/SymfonyStorage.php
vendored
Normal file
55
vendor/itsgoingd/clockwork/Clockwork/Storage/SymfonyStorage.php
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php namespace Clockwork\Storage;
|
||||
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Support\Symfony\ProfileTransformer;
|
||||
|
||||
use Symfony\Component\HttpKernel\Profiler\Profiler;
|
||||
|
||||
// Storage wrapping Symfony profiler
|
||||
class SymfonyStorage extends FileStorage
|
||||
{
|
||||
// Symfony profiler instance
|
||||
protected $profiler;
|
||||
|
||||
// Symfony profiler path
|
||||
protected $path;
|
||||
|
||||
// Create a new instance, takes Symfony profiler instance and path as argument
|
||||
public function __construct(Profiler $profiler, $path)
|
||||
{
|
||||
$this->profiler = $profiler;
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
// Store request, no-op since this is read-only storage implementation
|
||||
public function store(Request $request, $skipIndex = false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Cleanup old requests, no-op since this is read-only storage implementation
|
||||
public function cleanup($force = false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
protected function loadRequest($token)
|
||||
{
|
||||
return ($profile = $this->profiler->loadProfile($token)) ? (new ProfileTransformer)->transform($profile) : null;
|
||||
}
|
||||
|
||||
// Open index file, optionally move file pointer to the end
|
||||
protected function openIndex($position = 'start', $lock = null, $force = null)
|
||||
{
|
||||
$this->indexHandle = fopen("{$this->path}/index.csv", 'r');
|
||||
|
||||
if ($position == 'end') fseek($this->indexHandle, 0, SEEK_END);
|
||||
}
|
||||
|
||||
protected function makeRequestFromIndex($record)
|
||||
{
|
||||
return new Request(array_combine(
|
||||
[ 'id', 'ip', 'method', 'uri', 'time', 'parent', 'responseStatus' ], $record
|
||||
));
|
||||
}
|
||||
}
|
43
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkCleanCommand.php
vendored
Normal file
43
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkCleanCommand.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php namespace Clockwork\Support\Laravel;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
// Console command for cleaning old requests metadata
|
||||
class ClockworkCleanCommand extends Command
|
||||
{
|
||||
// Command name
|
||||
protected $name = 'clockwork:clean';
|
||||
|
||||
// Command description
|
||||
protected $description = 'Cleans Clockwork request metadata';
|
||||
|
||||
// Command options
|
||||
public function getOptions()
|
||||
{
|
||||
return [
|
||||
[ 'all', 'a', InputOption::VALUE_NONE, 'cleans all data' ],
|
||||
[ 'expiration', 'e', InputOption::VALUE_REQUIRED, 'cleans data older than specified value in minutes' ]
|
||||
];
|
||||
}
|
||||
|
||||
// Execute the console command
|
||||
public function handle()
|
||||
{
|
||||
if ($this->option('all')) {
|
||||
$this->laravel['config']->set('clockwork.storage_expiration', 0);
|
||||
} elseif ($expiration = $this->option('expiration')) {
|
||||
$this->laravel['config']->set('clockwork.storage_expiration', $expiration);
|
||||
}
|
||||
|
||||
$this->laravel['clockwork.support']->makeStorage()->cleanup($force = true);
|
||||
|
||||
$this->info('Metadata cleaned successfully.');
|
||||
}
|
||||
|
||||
// Compatibility for the old Laravel versions
|
||||
public function fire()
|
||||
{
|
||||
return $this->handle();
|
||||
}
|
||||
}
|
91
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkController.php
vendored
Normal file
91
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkController.php
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php namespace Clockwork\Support\Laravel;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\Support\Laravel\ClockworkSupport;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Laravel\Telescope\Telescope;
|
||||
|
||||
// Clockwork api and app controller
|
||||
class ClockworkController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(config('clockwork.middlewares'));
|
||||
}
|
||||
|
||||
// Authantication endpoint
|
||||
public function authenticate(Clockwork $clockwork, ClockworkSupport $clockworkSupport, Request $request)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled($clockworkSupport);
|
||||
|
||||
$token = $clockwork->authenticator()->attempt(
|
||||
$request->only([ 'username', 'password' ])
|
||||
);
|
||||
|
||||
return new JsonResponse([ 'token' => $token ], $token ? 200 : 403);
|
||||
}
|
||||
|
||||
// Metadata retrieving endpoint
|
||||
public function getData(ClockworkSupport $clockworkSupport, Request $request, $id = null, $direction = null, $count = null)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled($clockworkSupport);
|
||||
|
||||
return $clockworkSupport->getData(
|
||||
$id, $direction, $count, $request->only([ 'only', 'except' ])
|
||||
);
|
||||
}
|
||||
|
||||
// Extended metadata retrieving endpoint
|
||||
public function getExtendedData(ClockworkSupport $clockworkSupport, Request $request, $id = null)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled($clockworkSupport);
|
||||
|
||||
return $clockworkSupport->getExtendedData(
|
||||
$id, $request->only([ 'only', 'except' ])
|
||||
);
|
||||
}
|
||||
|
||||
// Metadata updating endpoint
|
||||
public function updateData(ClockworkSupport $clockworkSupport, Request $request, $id = null)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled($clockworkSupport);
|
||||
|
||||
return $clockworkSupport->updateData($id, $request->json()->all());
|
||||
}
|
||||
|
||||
// App index
|
||||
public function webIndex(ClockworkSupport $clockworkSupport)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled($clockworkSupport);
|
||||
|
||||
return $clockworkSupport->getWebAsset('index.html');
|
||||
}
|
||||
|
||||
// App assets serving
|
||||
public function webAsset(ClockworkSupport $clockworkSupport, $path)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled($clockworkSupport);
|
||||
|
||||
return $clockworkSupport->getWebAsset($path);
|
||||
}
|
||||
|
||||
// App redirect (/clockwork -> /clockwork/app)
|
||||
public function webRedirect(ClockworkSupport $clockworkSupport, Request $request)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled($clockworkSupport);
|
||||
|
||||
return new RedirectResponse('/' . $request->path() . '/app');
|
||||
}
|
||||
|
||||
// Ensure Clockwork is still enabled at this point and stop Telescope recording if present
|
||||
protected function ensureClockworkIsEnabled(ClockworkSupport $clockworkSupport)
|
||||
{
|
||||
if (class_exists(Telescope::class)) Telescope::stopRecording();
|
||||
|
||||
if (! $clockworkSupport->isEnabled()) abort(404);
|
||||
}
|
||||
}
|
38
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkMiddleware.php
vendored
Normal file
38
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkMiddleware.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php namespace Clockwork\Support\Laravel;
|
||||
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
|
||||
// Clockwork Laravel middleware
|
||||
class ClockworkMiddleware
|
||||
{
|
||||
// Laravel application instance
|
||||
protected $app;
|
||||
|
||||
// Create a new middleware instance
|
||||
public function __construct(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
// Handle an incoming request
|
||||
public function handle($request, \Closure $next)
|
||||
{
|
||||
$this->app['clockwork']->event('Controller')->begin();
|
||||
|
||||
try {
|
||||
$response = $next($request);
|
||||
} catch (\Exception $e) {
|
||||
$this->app[ExceptionHandler::class]->report($e);
|
||||
$response = $this->app[ExceptionHandler::class]->render($request, $e);
|
||||
}
|
||||
|
||||
return $this->app['clockwork.support']->processRequest($request, $response);
|
||||
}
|
||||
|
||||
// Record the current request after a response is sent
|
||||
public function terminate()
|
||||
{
|
||||
$this->app['clockwork.support']->recordRequest();
|
||||
}
|
||||
}
|
274
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkServiceProvider.php
vendored
Normal file
274
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkServiceProvider.php
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
<?php namespace Clockwork\Support\Laravel;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\Authentication\AuthenticatorInterface;
|
||||
use Clockwork\DataSource\EloquentDataSource;
|
||||
use Clockwork\DataSource\LaravelCacheDataSource;
|
||||
use Clockwork\DataSource\LaravelDataSource;
|
||||
use Clockwork\DataSource\LaravelEventsDataSource;
|
||||
use Clockwork\DataSource\LaravelNotificationsDataSource;
|
||||
use Clockwork\DataSource\LaravelQueueDataSource;
|
||||
use Clockwork\DataSource\LaravelRedisDataSource;
|
||||
use Clockwork\DataSource\LaravelTwigDataSource;
|
||||
use Clockwork\DataSource\LaravelViewsDataSource;
|
||||
use Clockwork\DataSource\SwiftDataSource;
|
||||
use Clockwork\DataSource\TwigDataSource;
|
||||
use Clockwork\DataSource\XdebugDataSource;
|
||||
use Clockwork\Helpers\StackFilter;
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Storage\StorageInterface;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ClockworkServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot()
|
||||
{
|
||||
if ($this->app['clockwork.support']->isCollectingData()) {
|
||||
$this->registerEventListeners();
|
||||
$this->registerMiddleware();
|
||||
}
|
||||
|
||||
$this->app['clockwork.support']->handleArtisanEvents();
|
||||
$this->app['clockwork.support']->handleOctaneEvents();
|
||||
|
||||
// If Clockwork is disabled, return before registering middleware or routes
|
||||
if (! $this->app['clockwork.support']->isEnabled()) return;
|
||||
|
||||
$this->registerRoutes();
|
||||
|
||||
// register the Clockwork Web UI routes
|
||||
if ($this->app['clockwork.support']->isWebEnabled()) {
|
||||
$this->registerWebRoutes();
|
||||
}
|
||||
}
|
||||
|
||||
public function register()
|
||||
{
|
||||
$this->registerConfiguration();
|
||||
$this->registerClockwork();
|
||||
$this->registerCommands();
|
||||
$this->registerDataSources();
|
||||
$this->registerAliases();
|
||||
|
||||
$this->app->make('clockwork.request'); // instantiate the request to have id and time available as early as possible
|
||||
|
||||
$this->app['clockwork.support']
|
||||
->configureSerializer()
|
||||
->configureShouldCollect()
|
||||
->configureShouldRecord();
|
||||
|
||||
if ($this->app['clockwork.support']->getConfig('register_helpers', true)) {
|
||||
require __DIR__ . '/helpers.php';
|
||||
}
|
||||
}
|
||||
|
||||
// Register the configuration file
|
||||
protected function registerConfiguration()
|
||||
{
|
||||
$this->publishes([ __DIR__ . '/config/clockwork.php' => config_path('clockwork.php') ]);
|
||||
$this->mergeConfigFrom(__DIR__ . '/config/clockwork.php', 'clockwork');
|
||||
}
|
||||
|
||||
// Register core Clockwork components
|
||||
protected function registerClockwork()
|
||||
{
|
||||
$this->app->singleton('clockwork', function ($app) {
|
||||
return (new Clockwork)
|
||||
->authenticator($app['clockwork.authenticator'])
|
||||
->request($app['clockwork.request'])
|
||||
->storage($app['clockwork.storage']);
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.authenticator', function ($app) {
|
||||
return $app['clockwork.support']->makeAuthenticator();
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.request', function ($app) {
|
||||
return new Request;
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.storage', function ($app) {
|
||||
return $app['clockwork.support']->makeStorage();
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.support', function ($app) {
|
||||
return new ClockworkSupport($app);
|
||||
});
|
||||
}
|
||||
|
||||
// Register the artisan commands
|
||||
protected function registerCommands()
|
||||
{
|
||||
$this->commands([
|
||||
ClockworkCleanCommand::class
|
||||
]);
|
||||
}
|
||||
|
||||
// Register Clockwork data sources
|
||||
protected function registerDataSources()
|
||||
{
|
||||
$this->app->singleton('clockwork.cache', function ($app) {
|
||||
return (new LaravelCacheDataSource(
|
||||
$app['events'],
|
||||
$app['clockwork.support']->getConfig('features.cache.collect_queries'),
|
||||
$app['clockwork.support']->getConfig('features.cache.collect_values')
|
||||
));
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.eloquent', function ($app) {
|
||||
$dataSource = (new EloquentDataSource(
|
||||
$app['db'],
|
||||
$app['events'],
|
||||
$app['clockwork.support']->getConfig('features.database.collect_queries'),
|
||||
$app['clockwork.support']->getConfig('features.database.slow_threshold'),
|
||||
$app['clockwork.support']->getConfig('features.database.slow_only'),
|
||||
$app['clockwork.support']->getConfig('features.database.detect_duplicate_queries'),
|
||||
$app['clockwork.support']->getConfig('features.database.collect_models_actions'),
|
||||
$app['clockwork.support']->getConfig('features.database.collect_models_retrieved')
|
||||
));
|
||||
|
||||
// if we are collecting queue jobs, filter out queries caused by the database queue implementation
|
||||
if ($app['clockwork.support']->isCollectingQueueJobs()) {
|
||||
$dataSource->addFilter(function ($query, $trace) {
|
||||
return ! $trace->first(StackFilter::make()->isClass(\Illuminate\Queue\DatabaseQueue::class));
|
||||
}, 'early');
|
||||
}
|
||||
|
||||
if ($app->runningUnitTests()) {
|
||||
$dataSource->addFilter(function ($query, $trace) {
|
||||
return ! $trace->first(StackFilter::make()->isClass([
|
||||
\Illuminate\Database\Migrations\Migrator::class,
|
||||
\Illuminate\Database\Console\Migrations\MigrateCommand::class
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
return $dataSource;
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.events', function ($app) {
|
||||
return (new LaravelEventsDataSource(
|
||||
$app['events'],
|
||||
$app['clockwork.support']->getConfig('features.events.ignored_events', [])
|
||||
));
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.laravel', function ($app) {
|
||||
return (new LaravelDataSource(
|
||||
$app,
|
||||
$app['clockwork.support']->isFeatureEnabled('log'),
|
||||
$app['clockwork.support']->isFeatureEnabled('routes'),
|
||||
$app['clockwork.support']->getConfig('features.routes.only_namespaces', [])
|
||||
));
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.notifications', function ($app) {
|
||||
return new LaravelNotificationsDataSource($app['events']);
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.queue', function ($app) {
|
||||
return (new LaravelQueueDataSource($app['queue']->connection()));
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.redis', function ($app) {
|
||||
$dataSource = new LaravelRedisDataSource($app['events']);
|
||||
|
||||
// if we are collecting queue jobs, filter out commands executed by the redis queue implementation
|
||||
if ($app['clockwork.support']->isCollectingQueueJobs()) {
|
||||
$dataSource->addFilter(function ($query, $trace) {
|
||||
return ! $trace->first(StackFilter::make()->isClass([
|
||||
\Illuminate\Queue\RedisQueue::class,
|
||||
\Laravel\Horizon\Repositories\RedisJobRepository::class,
|
||||
\Laravel\Horizon\Repositories\RedisTagRepository::class,
|
||||
\Laravel\Horizon\Repositories\RedisMetricsRepository::class
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
return $dataSource;
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.swift', function ($app) {
|
||||
return new SwiftDataSource($app['mailer']->getSwiftMailer());
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.twig', function ($app) {
|
||||
return new TwigDataSource($app['twig']);
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.views', function ($app) {
|
||||
return new LaravelViewsDataSource(
|
||||
$app['events'],
|
||||
$app['clockwork.support']->getConfig('features.views.collect_data')
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->singleton('clockwork.xdebug', function ($app) {
|
||||
return new XdebugDataSource;
|
||||
});
|
||||
}
|
||||
|
||||
// Set up aliases for all Clockwork parts so they can be resolved by type-hinting
|
||||
protected function registerAliases()
|
||||
{
|
||||
$this->app->alias('clockwork', Clockwork::class);
|
||||
|
||||
$this->app->alias('clockwork.authenticator', AuthenticatorInterface::class);
|
||||
$this->app->alias('clockwork.storage', StorageInterface::class);
|
||||
$this->app->alias('clockwork.support', ClockworkSupport::class);
|
||||
|
||||
$this->app->alias('clockwork.cache', LaravelCacheDataSource::class);
|
||||
$this->app->alias('clockwork.eloquent', EloquentDataSource::class);
|
||||
$this->app->alias('clockwork.events', LaravelEventsDataSource::class);
|
||||
$this->app->alias('clockwork.laravel', LaravelDataSource::class);
|
||||
$this->app->alias('clockwork.notifications', LaravelNotificationsDataSource::class);
|
||||
$this->app->alias('clockwork.queue', LaravelQueueDataSource::class);
|
||||
$this->app->alias('clockwork.redis', LaravelRedisDataSource::class);
|
||||
$this->app->alias('clockwork.swift', SwiftDataSource::class);
|
||||
$this->app->alias('clockwork.xdebug', XdebugDataSource::class);
|
||||
}
|
||||
|
||||
// Register event listeners
|
||||
protected function registerEventListeners()
|
||||
{
|
||||
$this->app->booted(function () {
|
||||
$this->app['clockwork.support']->addDataSources()->listenToEvents();
|
||||
});
|
||||
}
|
||||
|
||||
// Register middleware
|
||||
protected function registerMiddleware()
|
||||
{
|
||||
$kernel = $this->app[\Illuminate\Contracts\Http\Kernel::class];
|
||||
|
||||
if (method_exists($kernel, 'hasMiddleware') && $kernel->hasMiddleware(ClockworkMiddleware::class)) return;
|
||||
|
||||
$kernel->prependMiddleware(ClockworkMiddleware::class);
|
||||
}
|
||||
|
||||
protected function registerRoutes()
|
||||
{
|
||||
$this->app['router']->get('/__clockwork/{id}/extended', 'Clockwork\Support\Laravel\ClockworkController@getExtendedData')
|
||||
->where('id', '([0-9-]+|latest)');
|
||||
$this->app['router']->get('/__clockwork/{id}/{direction?}/{count?}', 'Clockwork\Support\Laravel\ClockworkController@getData')
|
||||
->where('id', '([0-9-]+|latest)')->where('direction', '(next|previous)')->where('count', '\d+');
|
||||
$this->app['router']->put('/__clockwork/{id}', 'Clockwork\Support\Laravel\ClockworkController@updateData');
|
||||
$this->app['router']->post('/__clockwork/auth', 'Clockwork\Support\Laravel\ClockworkController@authenticate');
|
||||
}
|
||||
|
||||
protected function registerWebRoutes()
|
||||
{
|
||||
$this->app['clockwork.support']->webPaths()->each(function ($path) {
|
||||
$this->app['router']->get("{$path}", 'Clockwork\Support\Laravel\ClockworkController@webRedirect');
|
||||
$this->app['router']->get("{$path}/app", 'Clockwork\Support\Laravel\ClockworkController@webIndex');
|
||||
$this->app['router']->get("{$path}/{path}", 'Clockwork\Support\Laravel\ClockworkController@webAsset')
|
||||
->where('path', '.+');
|
||||
});
|
||||
}
|
||||
|
||||
public function provides()
|
||||
{
|
||||
return [ 'clockwork' ];
|
||||
}
|
||||
}
|
734
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkSupport.php
vendored
Normal file
734
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkSupport.php
vendored
Normal file
@@ -0,0 +1,734 @@
|
||||
<?php namespace Clockwork\Support\Laravel;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\Authentication\NullAuthenticator;
|
||||
use Clockwork\Authentication\SimpleAuthenticator;
|
||||
use Clockwork\DataSource\PhpDataSource;
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\ServerTiming;
|
||||
use Clockwork\Helpers\StackFilter;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
use Clockwork\Request\IncomingRequest;
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Storage\FileStorage;
|
||||
use Clockwork\Storage\Search;
|
||||
use Clockwork\Storage\SqlStorage;
|
||||
use Clockwork\Web\Web;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Contracts\Console\Kernel as ConsoleKernel;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Redis\RedisManager;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
// Support class for the Laravel integration
|
||||
class ClockworkSupport
|
||||
{
|
||||
// Laravel application instance
|
||||
protected $app;
|
||||
|
||||
// Laravel artisan (console application) instance
|
||||
protected $artisan;
|
||||
|
||||
// Incoming request instance
|
||||
protected $incomingRequest;
|
||||
|
||||
public function __construct(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
// Get a value form the Clockwork config
|
||||
public function getConfig($key, $default = null)
|
||||
{
|
||||
return $this->app['config']->get("clockwork.{$key}", $default);
|
||||
}
|
||||
|
||||
// Retrieve metadata
|
||||
public function getData($id = null, $direction = null, $count = null, $filter = [], $extended = false)
|
||||
{
|
||||
if (isset($this->app['session'])) $this->app['session.store']->reflash();
|
||||
|
||||
$authenticator = $this->app['clockwork']->authenticator();
|
||||
$storage = $this->app['clockwork']->storage();
|
||||
|
||||
$authenticated = $authenticator->check($this->app['request']->header('X-Clockwork-Auth'));
|
||||
|
||||
if ($authenticated !== true) {
|
||||
return new JsonResponse([ 'message' => $authenticated, 'requires' => $authenticator->requires() ], 403);
|
||||
}
|
||||
|
||||
if ($direction == 'previous') {
|
||||
$data = $storage->previous($id, $count, Search::fromRequest($this->app['request']->all()));
|
||||
} elseif ($direction == 'next') {
|
||||
$data = $storage->next($id, $count, Search::fromRequest($this->app['request']->all()));
|
||||
} elseif ($id == 'latest') {
|
||||
$data = $storage->latest(Search::fromRequest($this->app['request']->all()));
|
||||
} else {
|
||||
$data = $storage->find($id);
|
||||
}
|
||||
|
||||
if ($extended) {
|
||||
$this->addDataSources();
|
||||
$this->app['clockwork']->extendRequest($data);
|
||||
}
|
||||
|
||||
$except = isset($filter['except']) ? explode(',', $filter['except']) : [];
|
||||
$only = isset($filter['only']) ? explode(',', $filter['only']) : null;
|
||||
|
||||
if (is_array($data)) {
|
||||
$data = array_map(function ($request) use ($except, $only) {
|
||||
return $only ? $request->only(array_diff($only, [ 'updateToken' ])) : $request->except(array_merge($except, [ 'updateToken' ]));
|
||||
}, $data);
|
||||
} elseif ($data) {
|
||||
$data = $only ? $data->only(array_diff($only, [ 'updateToken' ])) : $data->except(array_merge($except, [ 'updateToken' ]));
|
||||
}
|
||||
|
||||
return new JsonResponse($data);
|
||||
}
|
||||
|
||||
// Retrieve extended metadata
|
||||
public function getExtendedData($id, $filter = [])
|
||||
{
|
||||
return $this->getData($id, null, null, $filter, true);
|
||||
}
|
||||
|
||||
// Update metadata
|
||||
public function updateData($id, $input = [])
|
||||
{
|
||||
if (isset($this->app['session'])) $this->app['session.store']->reflash();
|
||||
|
||||
if (! $this->isCollectingClientMetrics()) {
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
$storage = $this->app['clockwork']->storage();
|
||||
|
||||
$request = $storage->find($id);
|
||||
|
||||
if (! $request) {
|
||||
return new JsonResponse([ 'message' => 'Request not found.' ], 404);
|
||||
}
|
||||
|
||||
$token = isset($input['_token']) ? $input['_token'] : '';
|
||||
|
||||
if (! $request->updateToken || ! hash_equals($request->updateToken, $token)) {
|
||||
return new JsonResponse([ 'message' => 'Invalid update token.' ], 403);
|
||||
}
|
||||
|
||||
foreach ($input as $key => $value) {
|
||||
if (in_array($key, [ 'clientMetrics', 'webVitals' ])) {
|
||||
$request->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$storage->update($request);
|
||||
}
|
||||
|
||||
// Return an asset for web ui based on it's path, resolves correct mime-type and protectes from accessing files
|
||||
// outside of Clockwork public dir
|
||||
public function getWebAsset($path)
|
||||
{
|
||||
$asset = (new Web)->asset($path);
|
||||
|
||||
if (! $asset) throw new NotFoundHttpException;
|
||||
|
||||
return new BinaryFileResponse($asset['path'], 200, [ 'Content-Type' => $asset['mime'] ]);
|
||||
}
|
||||
|
||||
// Add enabled data sources
|
||||
public function addDataSources()
|
||||
{
|
||||
$clockwork = $this->app['clockwork'];
|
||||
|
||||
$clockwork
|
||||
->addDataSource(new PhpDataSource)
|
||||
->addDataSource($this->frameworkDataSource());
|
||||
|
||||
if ($this->isFeatureEnabled('database')) $clockwork->addDataSource($this->app['clockwork.eloquent']);
|
||||
if ($this->isFeatureEnabled('cache')) $clockwork->addDataSource($this->app['clockwork.cache']);
|
||||
if ($this->isFeatureEnabled('redis')) $clockwork->addDataSource($this->app['clockwork.redis']);
|
||||
if ($this->isFeatureEnabled('queue')) $clockwork->addDataSource($this->app['clockwork.queue']);
|
||||
if ($this->isFeatureEnabled('events')) $clockwork->addDataSource($this->app['clockwork.events']);
|
||||
if ($this->isFeatureEnabled('notifications')) {
|
||||
$clockwork->addDataSource(
|
||||
$this->isFeatureAvailable('notifications-events')
|
||||
? $this->app['clockwork.notifications'] : $this->app['clockwork.swift']
|
||||
);
|
||||
}
|
||||
if ($this->isFeatureAvailable('xdebug')) $clockwork->addDataSource($this->app['clockwork.xdebug']);
|
||||
if ($this->isFeatureEnabled('views')) {
|
||||
$clockwork->addDataSource(
|
||||
$this->getConfig('features.views.use_twig_profiler', false)
|
||||
? $this->app['clockwork.twig'] : $this->app['clockwork.views']
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Start listening to events
|
||||
public function listenToEvents()
|
||||
{
|
||||
$this->frameworkDataSource()->listenToEvents();
|
||||
|
||||
if ($this->isFeatureEnabled('cache')) $this->app['clockwork.cache']->listenToEvents();
|
||||
if ($this->isFeatureEnabled('database')) $this->app['clockwork.eloquent']->listenToEvents();
|
||||
if ($this->isFeatureEnabled('events')) $this->app['clockwork.events']->listenToEvents();
|
||||
if ($this->isFeatureEnabled('notifications')) {
|
||||
$this->isFeatureAvailable('notifications-events')
|
||||
? $this->app['clockwork.notifications']->listenToEvents() : $this->app['clockwork.swift']->listenToEvents();
|
||||
}
|
||||
if ($this->isFeatureEnabled('queue')) {
|
||||
$this->app['clockwork.queue']->listenToEvents();
|
||||
$this->app['clockwork.queue']->setCurrentRequestId($this->app['clockwork.request']->id);
|
||||
}
|
||||
if ($this->isFeatureEnabled('redis')) {
|
||||
$this->app[RedisManager::class]->enableEvents();
|
||||
$this->app['clockwork.redis']->listenToEvents();
|
||||
}
|
||||
if ($this->isFeatureEnabled('views')) {
|
||||
$this->getConfig('features.views.use_twig_profiler', false)
|
||||
? $this->app['clockwork.twig']->listenToEvents() : $this->app['clockwork.views']->listenToEvents();
|
||||
}
|
||||
|
||||
if ($this->isCollectingCommands()) $this->collectCommands();
|
||||
if ($this->isCollectingQueueJobs()) $this->collectQueueJobs();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Resolves the framework data source from the container
|
||||
protected function frameworkDataSource()
|
||||
{
|
||||
return $this->app['clockwork.laravel'];
|
||||
}
|
||||
|
||||
public function handleArtisanEvents()
|
||||
{
|
||||
if (class_exists(\Illuminate\Console\Events\ArtisanStarting::class)) {
|
||||
$this->app['events']->listen(\Illuminate\Console\Events\ArtisanStarting::class, function ($event) {
|
||||
$this->artisan = $event->artisan;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function handleOctaneEvents()
|
||||
{
|
||||
$this->app['events']->listen(\Laravel\Octane\Events\RequestReceived::class, function ($event) {
|
||||
$this->app = $event->sandbox;
|
||||
$this->incomingRequest = null;
|
||||
|
||||
$this->app->forgetInstance('clockwork.request');
|
||||
$request = $this->app->make('clockwork.request')->override('requestTime', microtime(true));
|
||||
|
||||
$this->app['clockwork']->reset()->request($request);
|
||||
$this->app['clockwork.laravel']->setApplication($this->app);
|
||||
});
|
||||
}
|
||||
|
||||
// Make a storage instance based on the current configuration
|
||||
public function makeStorage()
|
||||
{
|
||||
$expiration = $this->getConfig('storage_expiration');
|
||||
|
||||
if ($this->getConfig('storage', 'files') == 'sql') {
|
||||
$database = $this->getConfig('storage_sql_database', storage_path('clockwork.sqlite'));
|
||||
$table = $this->getConfig('storage_sql_table', 'clockwork');
|
||||
|
||||
if ($this->app['config']->get("database.connections.{$database}")) {
|
||||
$database = $this->app['db']->connection($database)->getPdo();
|
||||
} else {
|
||||
$database = "sqlite:{$database}";
|
||||
}
|
||||
|
||||
return new SqlStorage($database, $table, null, null, $expiration);
|
||||
} else {
|
||||
return new FileStorage(
|
||||
$this->getConfig('storage_files_path', storage_path('clockwork')),
|
||||
0700,
|
||||
$expiration,
|
||||
$this->getConfig('storage_files_compress', false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Make an authenticator instance based on the current configuration
|
||||
public function makeAuthenticator()
|
||||
{
|
||||
$authenticator = $this->getConfig('authentication');
|
||||
|
||||
if (is_string($authenticator)) {
|
||||
return $this->app->make($authenticator);
|
||||
} elseif ($authenticator) {
|
||||
return new SimpleAuthenticator($this->getConfig('authentication_password'));
|
||||
} else {
|
||||
return new NullAuthenticator;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up collecting of executed artisan commands
|
||||
public function collectCommands()
|
||||
{
|
||||
$this->app['events']->listen(\Illuminate\Console\Events\CommandStarting::class, function ($event) {
|
||||
// only collect commands ran through artisan cli, other commands are recorded as part of respective request
|
||||
if (basename(StackTrace::get()->last()->file) != 'artisan') return;
|
||||
|
||||
if (! $this->getConfig('artisan.collect_output')) return;
|
||||
if (! $event->command || $this->isCommandFiltered($event->command)) return;
|
||||
|
||||
$event->output->setFormatter(
|
||||
version_compare(\Illuminate\Foundation\Application::VERSION, '9.0.0', '<')
|
||||
? new Console\CapturingLegacyFormatter($event->output->getFormatter())
|
||||
: new Console\CapturingFormatter($event->output->getFormatter())
|
||||
);
|
||||
});
|
||||
|
||||
$this->app['events']->listen(\Illuminate\Console\Events\CommandFinished::class, function ($event) {
|
||||
// only collect commands ran through artisan cli, other commands are recorded as part of respective request
|
||||
if (basename(StackTrace::get()->last()->file) != 'artisan') return;
|
||||
|
||||
if (! $event->command || $this->isCommandFiltered($event->command)) return;
|
||||
|
||||
$command = $this->artisan->find($event->command);
|
||||
|
||||
$allArguments = $event->input->getArguments();
|
||||
$allOptions = $event->input->getOptions();
|
||||
|
||||
$defaultArguments = $command->getDefinition()->getArgumentDefaults();
|
||||
$defaultOptions = $command->getDefinition()->getOptionDefaults();
|
||||
|
||||
$this->app->make('clockwork')
|
||||
->resolveAsCommand(
|
||||
$event->command,
|
||||
$event->exitCode,
|
||||
array_udiff_assoc($allArguments, $defaultArguments, function ($a, $b) { return $a == $b ? 0 : 1; }),
|
||||
array_udiff_assoc($allOptions, $defaultOptions, function ($a, $b) { return $a == $b ? 0 : 1; }),
|
||||
$defaultArguments,
|
||||
$defaultOptions,
|
||||
$this->getConfig('artisan.collect_output') ? $event->output->getFormatter()->capturedOutput() : null
|
||||
)
|
||||
->storeRequest();
|
||||
});
|
||||
}
|
||||
|
||||
// Set up collecting of executed queue jobs
|
||||
public function collectQueueJobs()
|
||||
{
|
||||
$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessing::class, function ($event) {
|
||||
// sync jobs are recorded as part of the parent request
|
||||
if ($event->job instanceof \Illuminate\Queue\Jobs\SyncJob) return;
|
||||
|
||||
$payload = $event->job->payload();
|
||||
|
||||
if (! isset($payload['clockwork_id']) || $this->isQueueJobFiltered($payload['displayName'])) return;
|
||||
|
||||
$request = new Request([ 'id' => $payload['clockwork_id'] ]);
|
||||
if (isset($payload['clockwork_parent_id'])) $request->setParent($payload['clockwork_parent_id']);
|
||||
|
||||
$this->app->make('clockwork')->reset()->request($request);
|
||||
|
||||
$this->app['clockwork.queue']->setCurrentRequestId($request->id);
|
||||
});
|
||||
|
||||
$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessed::class, function ($event) {
|
||||
$this->processQueueJob($event->job);
|
||||
});
|
||||
|
||||
$this->app['events']->listen(\Illuminate\Queue\Events\JobFailed::class, function ($event) {
|
||||
$this->processQueueJob($event->job, $event->exception);
|
||||
});
|
||||
}
|
||||
|
||||
// Process an executed queue job, resolves and records the current request
|
||||
protected function processQueueJob($job, $exception = null)
|
||||
{
|
||||
// sync jobs are recorded as part of the parent request
|
||||
if ($job instanceof \Illuminate\Queue\Jobs\SyncJob) return;
|
||||
|
||||
$payload = $job->payload();
|
||||
|
||||
if (! isset($payload['clockwork_id'])) return;
|
||||
|
||||
$unserialized = isset($payload['data']['command']) ? unserialize($payload['data']['command']) : null;
|
||||
|
||||
if (! $unserialized || $this->isQueueJobFiltered(get_class($unserialized))) return;
|
||||
|
||||
if ($exception) {
|
||||
$this->app->make('clockwork')->error($exception->getMessage(), [ 'exception' => $exception ]);
|
||||
}
|
||||
|
||||
$this->app->make('clockwork')
|
||||
->resolveAsQueueJob(
|
||||
get_class($unserialized),
|
||||
$payload['displayName'],
|
||||
$job->hasFailed() ? 'failed' : ($job->isReleased() ? 'released' : 'done'),
|
||||
$unserialized,
|
||||
$job->getQueue(),
|
||||
$job->getConnectionName(),
|
||||
array_filter([
|
||||
'maxTries' => isset($payload['maxTries']) ? $payload['maxTries'] : null,
|
||||
'delaySeconds' => isset($payload['delaySeconds']) ? $payload['delaySeconds'] : null,
|
||||
'timeout' => isset($payload['timeout']) ? $payload['timeout'] : null,
|
||||
'timeoutAt' => isset($payload['timeoutAt']) ? $payload['timeoutAt'] : null
|
||||
])
|
||||
)
|
||||
->storeRequest();
|
||||
}
|
||||
|
||||
// Process an executed http request, resolves the current request, sets Clockwork headers and cookies
|
||||
public function processRequest($request, $response)
|
||||
{
|
||||
if (! $this->isCollectingRequests()) {
|
||||
return $response; // Clockwork is not collecting data, additional check when the middleware is enabled manually
|
||||
}
|
||||
|
||||
$clockwork = $this->app['clockwork'];
|
||||
$clockworkRequest = $clockwork->request();
|
||||
|
||||
$clockwork->event('Controller')->end();
|
||||
|
||||
$this->setResponse($response);
|
||||
|
||||
$clockwork->resolveRequest();
|
||||
|
||||
if (! $this->isEnabled() || ! $this->isRecording($clockworkRequest)) {
|
||||
return $response; // Clockwork is disabled or we are not recording this request
|
||||
}
|
||||
|
||||
$response->headers->set('X-Clockwork-Id', $clockworkRequest->id, true);
|
||||
$response->headers->set('X-Clockwork-Version', Clockwork::VERSION, true);
|
||||
|
||||
if ($request->getBasePath()) {
|
||||
$response->headers->set('X-Clockwork-Path', $request->getBasePath() . '/__clockwork/', true);
|
||||
}
|
||||
|
||||
foreach ($this->getConfig('headers', []) as $headerName => $headerValue) {
|
||||
$response->headers->set("X-Clockwork-Header-{$headerName}", $headerValue);
|
||||
}
|
||||
|
||||
foreach ($clockwork->request()->subrequests as $subrequest) {
|
||||
$url = urlencode($subrequest['url']);
|
||||
$path = urlencode($subrequest['path']);
|
||||
|
||||
$response->headers->set('X-Clockwork-Subrequest', "{$subrequest['id']};{$url};{$path}", false);
|
||||
}
|
||||
|
||||
$this->appendServerTimingHeader($response, $clockworkRequest);
|
||||
|
||||
if (! ($response instanceof Response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($this->isCollectingClientMetrics() || $this->isToolbarEnabled()) {
|
||||
$clockworkBrowser = [
|
||||
'requestId' => $clockworkRequest->id,
|
||||
'version' => Clockwork::VERSION,
|
||||
'path' => $request->getBasePath() . '/__clockwork/',
|
||||
'webPath' => $request->getBasePath() . '/' . $this->webPaths()[0] . '/app',
|
||||
'token' => $clockworkRequest->updateToken,
|
||||
'metrics' => $this->isCollectingClientMetrics(),
|
||||
'toolbar' => $this->isToolbarEnabled()
|
||||
];
|
||||
|
||||
$response->cookie(
|
||||
new Cookie('x-clockwork', json_encode($clockworkBrowser), time() + 60, null, null, null, false)
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Records the current http request
|
||||
public function recordRequest()
|
||||
{
|
||||
if (! $this->isCollectingRequests()) {
|
||||
return; // Clockwork is not collecting data, additional check when the middleware is enabled manually
|
||||
}
|
||||
|
||||
$clockwork = $this->app['clockwork'];
|
||||
|
||||
if (! $this->isRecording($clockwork->request())) {
|
||||
return; // Collecting data is disabled, return immediately
|
||||
}
|
||||
|
||||
$clockwork->storeRequest();
|
||||
}
|
||||
|
||||
// Set current http response on the framework data source
|
||||
protected function setResponse($response)
|
||||
{
|
||||
$this->app['clockwork.laravel']->setResponse($response);
|
||||
}
|
||||
|
||||
// Configure serializer defaults
|
||||
public function configureSerializer()
|
||||
{
|
||||
Serializer::defaults([
|
||||
'limit' => $this->getConfig('serialization_depth'),
|
||||
'blackbox' => $this->getConfig('serialization_blackbox'),
|
||||
'traces' => $this->getConfig('stack_traces.enabled', true),
|
||||
'tracesSkip' => StackFilter::make()
|
||||
->isNotVendor(array_merge(
|
||||
$this->getConfig('stack_traces.skip_vendors', []),
|
||||
[ 'itsgoingd', 'laravel', 'illuminate', 'psr' ]
|
||||
))
|
||||
->isNotNamespace($this->getConfig('stack_traces.skip_namespaces', []))
|
||||
->isNotFunction([ 'call_user_func', 'call_user_func_array' ])
|
||||
->isNotClass($this->getConfig('stack_traces.skip_classes', [])),
|
||||
'tracesLimit' => $this->getConfig('stack_traces.limit', 10)
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Configure should collect rules
|
||||
public function configureShouldCollect()
|
||||
{
|
||||
$this->app['clockwork']->shouldCollect([
|
||||
'onDemand' => $this->getConfig('requests.on_demand', false),
|
||||
'sample' => $this->getConfig('requests.sample', false),
|
||||
'except' => $this->getConfig('requests.except', []),
|
||||
'only' => $this->getConfig('requests.only', []),
|
||||
'exceptPreflight' => $this->getConfig('requests.except_preflight', [])
|
||||
]);
|
||||
|
||||
// don't collect data for Clockwork requests
|
||||
$webPath = $this->webPaths()[0];
|
||||
$this->app['clockwork']->shouldCollect()->except([ '/__clockwork(?:/.*)?', "/{$webPath}(?:/.*)?" ]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Configure should record rules
|
||||
public function configureShouldRecord()
|
||||
{
|
||||
$this->app['clockwork']->shouldRecord([
|
||||
'errorsOnly' => $this->getConfig('requests.errors_only', false),
|
||||
'slowOnly' => $this->getConfig('requests.slow_only', false) ? $this->getConfig('requests.slow_threshold') : false
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Check whether Clockwork is enabled at all
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->getConfig('enable')
|
||||
|| $this->getConfig('enable') === null && $this->app['config']->get('app.debug');
|
||||
}
|
||||
|
||||
// Check whether we are collecting data
|
||||
public function isCollectingData()
|
||||
{
|
||||
return $this->isCollectingCommands()
|
||||
|| $this->isCollectingQueueJobs()
|
||||
|| $this->isCollectingRequests()
|
||||
|| $this->isCollectingTests();
|
||||
}
|
||||
|
||||
// Check whether we are collecting artisan commands
|
||||
public function isCollectingCommands()
|
||||
{
|
||||
return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
|
||||
&& $this->app->runningInConsole()
|
||||
&& $this->getConfig('artisan.collect', false);
|
||||
}
|
||||
|
||||
// Check whether we are collecting queue jobs
|
||||
public function isCollectingQueueJobs()
|
||||
{
|
||||
return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
|
||||
&& $this->app->runningInConsole()
|
||||
&& $this->getConfig('queue.collect', false);
|
||||
}
|
||||
|
||||
// Check whether we are collecting http requests
|
||||
public function isCollectingRequests()
|
||||
{
|
||||
return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
|
||||
&& ! $this->app->runningInConsole()
|
||||
&& $this->app['clockwork']->shouldCollect()->filter($this->incomingRequest());
|
||||
}
|
||||
|
||||
// Check whether we are collecting tests
|
||||
public function isCollectingTests()
|
||||
{
|
||||
return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
|
||||
&& $this->app->runningInConsole()
|
||||
&& $this->getConfig('tests.collect', false);
|
||||
}
|
||||
|
||||
// Check whether we are recording the passed request
|
||||
public function isRecording($incomingRequest)
|
||||
{
|
||||
return ($this->isEnabled() || $this->getConfig('collect_data_always', false))
|
||||
&& $this->app['clockwork']->shouldRecord()->filter($incomingRequest);
|
||||
}
|
||||
|
||||
// Check whether a feature is enabled
|
||||
public function isFeatureEnabled($feature)
|
||||
{
|
||||
return $this->getConfig("features.{$feature}.enabled") && $this->isFeatureAvailable($feature);
|
||||
}
|
||||
|
||||
// Check whether a feature is available
|
||||
public function isFeatureAvailable($feature)
|
||||
{
|
||||
if ($feature == 'database') {
|
||||
return $this->app['config']->get('database.default');
|
||||
} elseif ($feature == 'notifications-events') {
|
||||
return class_exists(\Illuminate\Mail\Events\MessageSent::class)
|
||||
&& class_exists(\Illuminate\Notifications\Events\NotificationSent::class);
|
||||
} elseif ($feature == 'redis') {
|
||||
return method_exists(\Illuminate\Redis\RedisManager::class, 'enableEvents');
|
||||
} elseif ($feature == 'queue') {
|
||||
return method_exists(\Illuminate\Queue\Queue::class, 'createPayloadUsing');
|
||||
} elseif ($feature == 'xdebug') {
|
||||
return in_array('xdebug', get_loaded_extensions());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check whether we are collecting client metrics
|
||||
public function isCollectingClientMetrics()
|
||||
{
|
||||
return $this->getConfig('features.performance.client_metrics', true);
|
||||
}
|
||||
|
||||
// Check whether the toolbar is enabled
|
||||
public function isToolbarEnabled()
|
||||
{
|
||||
return $this->getConfig('toolbar', false);
|
||||
}
|
||||
|
||||
// Check whether the web ui is enabled
|
||||
public function isWebEnabled()
|
||||
{
|
||||
return $this->getConfig('web', true);
|
||||
}
|
||||
|
||||
// Check whether a command should not be collected
|
||||
protected function isCommandFiltered($command)
|
||||
{
|
||||
$only = $this->getConfig('artisan.only', []);
|
||||
|
||||
if (count($only)) return ! in_array($command, $only);
|
||||
|
||||
$except = $this->getConfig('artisan.except', []);
|
||||
|
||||
if ($this->getConfig('artisan.except_laravel_commands', true)) {
|
||||
$except = array_merge($except, $this->builtinLaravelCommands());
|
||||
}
|
||||
|
||||
$except = array_merge($except, $this->builtinClockworkCommands());
|
||||
|
||||
return in_array($command, $except);
|
||||
}
|
||||
|
||||
// Check whether a queue job should not be collected
|
||||
protected function isQueueJobFiltered($queueJob)
|
||||
{
|
||||
$only = $this->getConfig('queue.only', []);
|
||||
|
||||
if (count($only)) return ! in_array($queueJob, $only);
|
||||
|
||||
$except = $this->getConfig('queue.except', []);
|
||||
|
||||
return in_array($queueJob, $except);
|
||||
}
|
||||
|
||||
// Check whether a test should not be collected
|
||||
public function isTestFiltered($test)
|
||||
{
|
||||
$except = $this->getConfig('tests.except', []);
|
||||
|
||||
return in_array($test, $except);
|
||||
}
|
||||
|
||||
// Append server timing headers from a Clockwork request to a http response
|
||||
protected function appendServerTimingHeader($response, $request)
|
||||
{
|
||||
if (($eventsCount = $this->getConfig('server_timing', 10)) !== false) {
|
||||
$response->headers->set('Server-Timing', ServerTiming::fromRequest($request, $eventsCount)->value());
|
||||
}
|
||||
}
|
||||
|
||||
// Make an incoming request instance
|
||||
protected function incomingRequest()
|
||||
{
|
||||
if ($this->incomingRequest) return $this->incomingRequest;
|
||||
|
||||
return $this->incomingRequest = new IncomingRequest([
|
||||
'method' => $this->app['request']->getMethod(),
|
||||
'uri' => $this->app['request']->getRequestUri(),
|
||||
'input' => $this->app['request']->input(),
|
||||
'cookies' => $this->app['request']->cookie()
|
||||
]);
|
||||
}
|
||||
|
||||
// Return an array of web ui paths
|
||||
public function webPaths()
|
||||
{
|
||||
$path = $this->getConfig('web', true);
|
||||
|
||||
if (is_string($path)) return collect([ trim($path, '/') ]);
|
||||
|
||||
return collect([ 'clockwork', '__clockwork' ]);
|
||||
}
|
||||
|
||||
// Return an array of built-in Laravel commands
|
||||
protected function builtinLaravelCommands()
|
||||
{
|
||||
return [
|
||||
'clear-compiled', 'completion', 'db', 'down', 'dump-server', 'env', 'help', 'list', 'migrate', 'optimize',
|
||||
'preset', 'serve', 'test', 'tinker', 'up',
|
||||
'app:name',
|
||||
'auth:clear-resets',
|
||||
'cache:clear', 'cache:forget', 'cache:table',
|
||||
'config:cache', 'config:clear',
|
||||
'db:seed', 'db:wipe',
|
||||
'event:cache', 'event:clear', 'event:generate', 'event:list',
|
||||
'horizon', 'horizon:clear', 'horizon:continue', 'horizon:continue-supervisor', 'horizon:forget',
|
||||
'horizon:install', 'horizon:list', 'horizon:pause', 'horizon:pause-supervisor', 'horizon:publish',
|
||||
'horizon:purge', 'horizon:snapshot', 'horizon:status', 'horizon:supervisors', 'horizon:terminate',
|
||||
'horizon:work',
|
||||
'key:generate',
|
||||
'make:auth', 'make:cast', 'make:channel', 'make:command', 'make:component', 'make:controller', 'make:event',
|
||||
'make:exception', 'make:factory', 'make:job', 'make:listener', 'make:mail', 'make:middleware',
|
||||
'make:migration', 'make:model', 'make:notification', 'make:observer', 'make:policy', 'make:provider',
|
||||
'make:request', 'make:resource', 'make:rule', 'make:scope', 'make:seeder', 'make:test',
|
||||
'migrate:fresh', 'migrate:install', 'migrate:refresh', 'migrate:reset', 'migrate:rollback',
|
||||
'migrate:status',
|
||||
'model:prune',
|
||||
'notifications:table',
|
||||
'octane:install', 'octane:reload', 'octane:start', 'octane:status', 'octane:stop',
|
||||
'optimize:clear',
|
||||
'package:discover',
|
||||
'queue:batches-table', 'queue:clear', 'queue:failed', 'queue:failed-table', 'queue:flush', 'queue:forget',
|
||||
'queue:listen', 'queue:monitor', 'queue:prune-batches', 'queue:prune-failed', 'queue:restart',
|
||||
'queue:retry', 'queue:retry-batch', 'queue:table', 'queue:work',
|
||||
'route:cache', 'route:clear', 'route:list',
|
||||
'sail:install', 'sail:publish',
|
||||
'schedule:clear-cache', 'schedule:list', 'schedule:run', 'schedule:test', 'schedule:work',
|
||||
'schema:dump',
|
||||
'session:table',
|
||||
'storage:link',
|
||||
'stub:publish',
|
||||
'vendor:publish',
|
||||
'view:cache', 'view:clear'
|
||||
];
|
||||
}
|
||||
|
||||
// Return an array of built-in Clockwork commands
|
||||
protected function builtinClockworkCommands()
|
||||
{
|
||||
return [
|
||||
'clockwork:clean'
|
||||
];
|
||||
}
|
||||
}
|
70
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Console/CapturingFormatter.php
vendored
Normal file
70
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Console/CapturingFormatter.php
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php namespace Clockwork\Support\Laravel\Console;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyleInterface;
|
||||
|
||||
// Formatter wrapping around a "real" formatter, capturing the formatted output (Symfony 6.x and later)
|
||||
class CapturingFormatter implements OutputFormatterInterface
|
||||
{
|
||||
protected $formatter;
|
||||
|
||||
protected $capturedOutput;
|
||||
|
||||
public function __construct(OutputFormatterInterface $formatter)
|
||||
{
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
public function capturedOutput()
|
||||
{
|
||||
$capturedOutput = $this->capturedOutput;
|
||||
|
||||
$this->capturedOutput = null;
|
||||
|
||||
return $capturedOutput;
|
||||
}
|
||||
|
||||
public function setDecorated(bool $decorated)
|
||||
{
|
||||
return $this->formatter->setDecorated($decorated);
|
||||
}
|
||||
|
||||
public function isDecorated(): bool
|
||||
{
|
||||
return $this->formatter->isDecorated();
|
||||
}
|
||||
|
||||
public function setStyle(string $name, OutputFormatterStyleInterface $style)
|
||||
{
|
||||
return $this->formatter->setStyle($name, $style);
|
||||
}
|
||||
|
||||
public function hasStyle(string $name): bool
|
||||
{
|
||||
return $this->formatter->hasStyle($name);
|
||||
}
|
||||
|
||||
public function getStyle(string $name): OutputFormatterStyleInterface
|
||||
{
|
||||
return $this->formatter->getStyle($name);
|
||||
}
|
||||
|
||||
public function format(?string $message): ?string
|
||||
{
|
||||
$formatted = $this->formatter->format($message);
|
||||
|
||||
$this->capturedOutput .= $formatted;
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
return $this->formatter->$method(...$args);
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->formatter = clone $this->formatter;
|
||||
}
|
||||
}
|
70
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Console/CapturingLegacyFormatter.php
vendored
Normal file
70
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Console/CapturingLegacyFormatter.php
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php namespace Clockwork\Support\Laravel\Console;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyleInterface;
|
||||
|
||||
// Formatter wrapping around a "real" formatter, capturing the formatted output (Symfony 5.x and earlier)
|
||||
class CapturingLegacyFormatter implements OutputFormatterInterface
|
||||
{
|
||||
protected $formatter;
|
||||
|
||||
protected $capturedOutput;
|
||||
|
||||
public function __construct(OutputFormatterInterface $formatter)
|
||||
{
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
public function capturedOutput()
|
||||
{
|
||||
$capturedOutput = $this->capturedOutput;
|
||||
|
||||
$this->capturedOutput = null;
|
||||
|
||||
return $capturedOutput;
|
||||
}
|
||||
|
||||
public function setDecorated($decorated)
|
||||
{
|
||||
return $this->formatter->setDecorated($decorated);
|
||||
}
|
||||
|
||||
public function isDecorated()
|
||||
{
|
||||
return $this->formatter->isDecorated();
|
||||
}
|
||||
|
||||
public function setStyle($name, OutputFormatterStyleInterface $style)
|
||||
{
|
||||
return $this->formatter->setStyle($name, $style);
|
||||
}
|
||||
|
||||
public function hasStyle($name)
|
||||
{
|
||||
return $this->formatter->hasStyle($name);
|
||||
}
|
||||
|
||||
public function getStyle($name)
|
||||
{
|
||||
return $this->formatter->getStyle($name);
|
||||
}
|
||||
|
||||
public function format($message)
|
||||
{
|
||||
$formatted = $this->formatter->format($message);
|
||||
|
||||
$this->capturedOutput .= $formatted;
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
return $this->formatter->$method(...$args);
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->formatter = clone $this->formatter;
|
||||
}
|
||||
}
|
27
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Eloquent/ResolveModelLegacyScope.php
vendored
Normal file
27
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Eloquent/ResolveModelLegacyScope.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php namespace Clockwork\Support\Laravel\Eloquent;
|
||||
|
||||
use Clockwork\DataSource\EloquentDataSource;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ScopeInterface;
|
||||
|
||||
class ResolveModelLegacyScope implements ScopeInterface
|
||||
{
|
||||
protected $dataSource;
|
||||
|
||||
public function __construct(EloquentDataSource $dataSource)
|
||||
{
|
||||
$this->dataSource = $dataSource;
|
||||
}
|
||||
|
||||
public function apply(Builder $builder, Model $model)
|
||||
{
|
||||
$this->dataSource->nextQueryModel = get_class($model);
|
||||
}
|
||||
|
||||
public function remove(Builder $builder, Model $model)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
}
|
22
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Eloquent/ResolveModelScope.php
vendored
Normal file
22
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Eloquent/ResolveModelScope.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php namespace Clockwork\Support\Laravel\Eloquent;
|
||||
|
||||
use Clockwork\DataSource\EloquentDataSource;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
class ResolveModelScope implements Scope
|
||||
{
|
||||
protected $dataSource;
|
||||
|
||||
public function __construct(EloquentDataSource $dataSource)
|
||||
{
|
||||
$this->dataSource = $dataSource;
|
||||
}
|
||||
|
||||
public function apply(Builder $builder, Model $model)
|
||||
{
|
||||
$this->dataSource->nextQueryModel = get_class($model);
|
||||
}
|
||||
}
|
9
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Facade.php
vendored
Normal file
9
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Facade.php
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php namespace Clockwork\Support\Laravel;
|
||||
|
||||
use Illuminate\Support\Facades\Facade as IlluminateFacade;
|
||||
|
||||
// Clockwork facade
|
||||
class Facade extends IlluminateFacade
|
||||
{
|
||||
protected static function getFacadeAccessor() { return 'clockwork'; }
|
||||
}
|
87
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Tests/UsesClockwork.php
vendored
Normal file
87
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Tests/UsesClockwork.php
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php namespace Clockwork\Support\Laravel\Tests;
|
||||
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\StackFilter;
|
||||
use Clockwork\Helpers\StackTrace;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use PHPUnit\Runner\BaseTestRunner;
|
||||
|
||||
// Trait to include in PHPUnit tests to collect executed tests
|
||||
trait UsesClockwork
|
||||
{
|
||||
// Clockwork phpunit metadata collected while executing tests
|
||||
protected static $clockwork = [
|
||||
'asserts' => []
|
||||
];
|
||||
|
||||
// Set up Clockwork in this test case, should be called from the PHPUnit setUp method
|
||||
protected function setUpClockwork()
|
||||
{
|
||||
if (! $this->app->make('clockwork.support')->isCollectingTests()) return;
|
||||
|
||||
$this->beforeApplicationDestroyed(function () {
|
||||
if ($this->app->make('clockwork.support')->isTestFiltered($this->toString())) return;
|
||||
|
||||
$this->app->make('clockwork')
|
||||
->resolveAsTest(
|
||||
$this->toString(),
|
||||
$this->resolveClockworkStatus(),
|
||||
$this->getStatusMessage(),
|
||||
$this->resolveClockworkAsserts()
|
||||
)
|
||||
->storeRequest();
|
||||
});
|
||||
}
|
||||
|
||||
// Resolve Clockwork test status
|
||||
protected function resolveClockworkStatus()
|
||||
{
|
||||
$status = $this->getStatus();
|
||||
|
||||
$statuses = [
|
||||
BaseTestRunner::STATUS_UNKNOWN => 'unknown',
|
||||
BaseTestRunner::STATUS_PASSED => 'passed',
|
||||
BaseTestRunner::STATUS_SKIPPED => 'skipped',
|
||||
BaseTestRunner::STATUS_INCOMPLETE => 'incomplete',
|
||||
BaseTestRunner::STATUS_FAILURE => 'failed',
|
||||
BaseTestRunner::STATUS_ERROR => 'error',
|
||||
BaseTestRunner::STATUS_RISKY => 'passed',
|
||||
BaseTestRunner::STATUS_WARNING => 'warning'
|
||||
];
|
||||
|
||||
return isset($statuses[$status]) ? $statuses[$status] : null;
|
||||
}
|
||||
|
||||
// Resolve executed asserts
|
||||
protected function resolveClockworkAsserts()
|
||||
{
|
||||
$asserts = static::$clockwork['asserts'];
|
||||
|
||||
if ($this->getStatus() == BaseTestRunner::STATUS_FAILURE && count($asserts)) {
|
||||
$asserts[count($asserts) - 1]['passed'] = false;
|
||||
}
|
||||
|
||||
static::$clockwork['asserts'] = [];
|
||||
|
||||
return $asserts;
|
||||
}
|
||||
|
||||
// Overload the main PHPUnit assert method to collect executed asserts
|
||||
public static function assertThat($value, Constraint $constraint, string $message = ''): void
|
||||
{
|
||||
$trace = StackTrace::get([ 'arguments' => true, 'limit' => 10 ]);
|
||||
|
||||
$assertFrame = $trace->filter(function ($frame) { return strpos($frame->function, 'assert') === 0; })->last();
|
||||
$trace = $trace->skip(StackFilter::make()->isNotVendor([ 'itsgoingd', 'phpunit' ]))->limit(3);
|
||||
|
||||
static::$clockwork['asserts'][] = [
|
||||
'name' => $assertFrame->function,
|
||||
'arguments' => $assertFrame->args,
|
||||
'trace' => (new Serializer)->trace($trace),
|
||||
'passed' => true
|
||||
];
|
||||
|
||||
parent::assertThat($value, $constraint, $message);
|
||||
}
|
||||
}
|
416
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/config/clockwork.php
vendored
Normal file
416
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/config/clockwork.php
vendored
Normal file
@@ -0,0 +1,416 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable Clockwork
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork is enabled by default only when your application is in debug mode. Here you can explicitly enable or
|
||||
| disable Clockwork. When disabled, no data is collected and the api and web ui are inactive.
|
||||
|
|
||||
*/
|
||||
|
||||
'enable' => env('CLOCKWORK_ENABLE', null),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Features
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| You can enable or disable various Clockwork features here. Some features have additional settings (eg. slow query
|
||||
| threshold for database queries).
|
||||
|
|
||||
*/
|
||||
|
||||
'features' => [
|
||||
|
||||
// Cache usage stats and cache queries including results
|
||||
'cache' => [
|
||||
'enabled' => env('CLOCKWORK_CACHE_ENABLED', true),
|
||||
|
||||
// Collect cache queries
|
||||
'collect_queries' => env('CLOCKWORK_CACHE_QUERIES', true),
|
||||
|
||||
// Collect values from cache queries (high performance impact with a very high number of queries)
|
||||
'collect_values' => env('CLOCKWORK_CACHE_COLLECT_VALUES', false)
|
||||
],
|
||||
|
||||
// Database usage stats and queries
|
||||
'database' => [
|
||||
'enabled' => env('CLOCKWORK_DATABASE_ENABLED', true),
|
||||
|
||||
// Collect database queries (high performance impact with a very high number of queries)
|
||||
'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', true),
|
||||
|
||||
// Collect details of models updates (high performance impact with a lot of model updates)
|
||||
'collect_models_actions' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_ACTIONS', true),
|
||||
|
||||
// Collect details of retrieved models (very high performance impact with a lot of models retrieved)
|
||||
'collect_models_retrieved' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_RETRIEVED', false),
|
||||
|
||||
// Query execution time threshold in milliseconds after which the query will be marked as slow
|
||||
'slow_threshold' => env('CLOCKWORK_DATABASE_SLOW_THRESHOLD'),
|
||||
|
||||
// Collect only slow database queries
|
||||
'slow_only' => env('CLOCKWORK_DATABASE_SLOW_ONLY', false),
|
||||
|
||||
// Detect and report duplicate queries
|
||||
'detect_duplicate_queries' => env('CLOCKWORK_DATABASE_DETECT_DUPLICATE_QUERIES', false)
|
||||
],
|
||||
|
||||
// Dispatched events
|
||||
'events' => [
|
||||
'enabled' => env('CLOCKWORK_EVENTS_ENABLED', true),
|
||||
|
||||
// Ignored events (framework events are ignored by default)
|
||||
'ignored_events' => [
|
||||
// App\Events\UserRegistered::class,
|
||||
// 'user.registered'
|
||||
],
|
||||
],
|
||||
|
||||
// Laravel log (you can still log directly to Clockwork with laravel log disabled)
|
||||
'log' => [
|
||||
'enabled' => env('CLOCKWORK_LOG_ENABLED', true)
|
||||
],
|
||||
|
||||
// Sent notifications
|
||||
'notifications' => [
|
||||
'enabled' => env('CLOCKWORK_NOTIFICATIONS_ENABLED', true),
|
||||
],
|
||||
|
||||
// Performance metrics
|
||||
'performance' => [
|
||||
// Allow collecting of client metrics. Requires separate clockwork-browser npm package.
|
||||
'client_metrics' => env('CLOCKWORK_PERFORMANCE_CLIENT_METRICS', true)
|
||||
],
|
||||
|
||||
// Dispatched queue jobs
|
||||
'queue' => [
|
||||
'enabled' => env('CLOCKWORK_QUEUE_ENABLED', true)
|
||||
],
|
||||
|
||||
// Redis commands
|
||||
'redis' => [
|
||||
'enabled' => env('CLOCKWORK_REDIS_ENABLED', true)
|
||||
],
|
||||
|
||||
// Routes list
|
||||
'routes' => [
|
||||
'enabled' => env('CLOCKWORK_ROUTES_ENABLED', false),
|
||||
|
||||
// Collect only routes from particular namespaces (only application routes by default)
|
||||
'only_namespaces' => [ 'App' ]
|
||||
],
|
||||
|
||||
// Rendered views
|
||||
'views' => [
|
||||
'enabled' => env('CLOCKWORK_VIEWS_ENABLED', true),
|
||||
|
||||
// Collect views including view data (high performance impact with a high number of views)
|
||||
'collect_data' => env('CLOCKWORK_VIEWS_COLLECT_DATA', false),
|
||||
|
||||
// Use Twig profiler instead of Laravel events for apps using laravel-twigbridge (more precise, but does
|
||||
// not support collecting view data)
|
||||
'use_twig_profiler' => env('CLOCKWORK_VIEWS_USE_TWIG_PROFILER', false)
|
||||
]
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable web UI
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork comes with a web UI accessible via http://your.app/clockwork. Here you can enable or disable this
|
||||
| feature. You can also set a custom path for the web UI.
|
||||
|
|
||||
*/
|
||||
|
||||
'web' => env('CLOCKWORK_WEB', true),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable toolbar
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can show a toolbar with basic metrics on all responses. Here you can enable or disable this feature.
|
||||
| Requires a separate clockwork-browser npm library.
|
||||
| For installation instructions see https://underground.works/clockwork/#docs-viewing-data
|
||||
|
|
||||
*/
|
||||
|
||||
'toolbar' => env('CLOCKWORK_TOOLBAR', true),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| HTTP requests collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork collects data about HTTP requests to your app. Here you can choose which requests should be collected.
|
||||
|
|
||||
*/
|
||||
|
||||
'requests' => [
|
||||
// With on-demand mode enabled, Clockwork will only profile requests when the browser extension is open or you
|
||||
// manually pass a "clockwork-profile" cookie or get/post data key.
|
||||
// Optionally you can specify a "secret" that has to be passed as the value to enable profiling.
|
||||
'on_demand' => env('CLOCKWORK_REQUESTS_ON_DEMAND', false),
|
||||
|
||||
// Collect only errors (requests with HTTP 4xx and 5xx responses)
|
||||
'errors_only' => env('CLOCKWORK_REQUESTS_ERRORS_ONLY', false),
|
||||
|
||||
// Response time threshold in milliseconds after which the request will be marked as slow
|
||||
'slow_threshold' => env('CLOCKWORK_REQUESTS_SLOW_THRESHOLD'),
|
||||
|
||||
// Collect only slow requests
|
||||
'slow_only' => env('CLOCKWORK_REQUESTS_SLOW_ONLY', false),
|
||||
|
||||
// Sample the collected requests (e.g. set to 100 to collect only 1 in 100 requests)
|
||||
'sample' => env('CLOCKWORK_REQUESTS_SAMPLE', false),
|
||||
|
||||
// List of URIs that should not be collected
|
||||
'except' => [
|
||||
'/horizon/.*', // Laravel Horizon requests
|
||||
'/telescope/.*', // Laravel Telescope requests
|
||||
'/_debugbar/.*', // Laravel DebugBar requests
|
||||
],
|
||||
|
||||
// List of URIs that should be collected, any other URI will not be collected if not empty
|
||||
'only' => [
|
||||
// '/api/.*'
|
||||
],
|
||||
|
||||
// Don't collect OPTIONS requests, mostly used in the CSRF pre-flight requests and are rarely of interest
|
||||
'except_preflight' => env('CLOCKWORK_REQUESTS_EXCEPT_PREFLIGHT', true)
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Artisan commands collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can collect data about executed artisan commands. Here you can enable and configure which commands
|
||||
| should be collected.
|
||||
|
|
||||
*/
|
||||
|
||||
'artisan' => [
|
||||
// Enable or disable collection of executed Artisan commands
|
||||
'collect' => env('CLOCKWORK_ARTISAN_COLLECT', false),
|
||||
|
||||
// List of commands that should not be collected (built-in commands are not collected by default)
|
||||
'except' => [
|
||||
// 'inspire'
|
||||
],
|
||||
|
||||
// List of commands that should be collected, any other command will not be collected if not empty
|
||||
'only' => [
|
||||
// 'inspire'
|
||||
],
|
||||
|
||||
// Enable or disable collection of command output
|
||||
'collect_output' => env('CLOCKWORK_ARTISAN_COLLECT_OUTPUT', false),
|
||||
|
||||
// Enable or disable collection of built-in Laravel commands
|
||||
'except_laravel_commands' => env('CLOCKWORK_ARTISAN_EXCEPT_LARAVEL_COMMANDS', true)
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Queue jobs collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can collect data about executed queue jobs. Here you can enable and configure which queue jobs should
|
||||
| be collected.
|
||||
|
|
||||
*/
|
||||
|
||||
'queue' => [
|
||||
// Enable or disable collection of executed queue jobs
|
||||
'collect' => env('CLOCKWORK_QUEUE_COLLECT', false),
|
||||
|
||||
// List of queue jobs that should not be collected
|
||||
'except' => [
|
||||
// App\Jobs\ExpensiveJob::class
|
||||
],
|
||||
|
||||
// List of queue jobs that should be collected, any other queue job will not be collected if not empty
|
||||
'only' => [
|
||||
// App\Jobs\BuggyJob::class
|
||||
]
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Tests collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can collect data about executed tests. Here you can enable and configure which tests should be
|
||||
| collected.
|
||||
|
|
||||
*/
|
||||
|
||||
'tests' => [
|
||||
// Enable or disable collection of ran tests
|
||||
'collect' => env('CLOCKWORK_TESTS_COLLECT', false),
|
||||
|
||||
// List of tests that should not be collected
|
||||
'except' => [
|
||||
// Tests\Unit\ExampleTest::class
|
||||
]
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable data collection when Clockwork is disabled
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| You can enable this setting to collect data even when Clockwork is disabled, e.g. for future analysis.
|
||||
|
|
||||
*/
|
||||
|
||||
'collect_data_always' => env('CLOCKWORK_COLLECT_DATA_ALWAYS', false),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Metadata storage
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Configure how is the metadata collected by Clockwork stored. Two options are available:
|
||||
| - files - A simple fast storage implementation storing data in one-per-request files.
|
||||
| - sql - Stores requests in a sql database. Supports MySQL, PostgreSQL and SQLite. Requires PDO.
|
||||
|
|
||||
*/
|
||||
|
||||
'storage' => env('CLOCKWORK_STORAGE', 'files'),
|
||||
|
||||
// Path where the Clockwork metadata is stored
|
||||
'storage_files_path' => env('CLOCKWORK_STORAGE_FILES_PATH', storage_path('clockwork')),
|
||||
|
||||
// Compress the metadata files using gzip, trading a little bit of performance for lower disk usage
|
||||
'storage_files_compress' => env('CLOCKWORK_STORAGE_FILES_COMPRESS', false),
|
||||
|
||||
// SQL database to use, can be a name of database configured in database.php or a path to a SQLite file
|
||||
'storage_sql_database' => env('CLOCKWORK_STORAGE_SQL_DATABASE', storage_path('clockwork.sqlite')),
|
||||
|
||||
// SQL table name to use, the table is automatically created and updated when needed
|
||||
'storage_sql_table' => env('CLOCKWORK_STORAGE_SQL_TABLE', 'clockwork'),
|
||||
|
||||
// Maximum lifetime of collected metadata in minutes, older requests will automatically be deleted, false to disable
|
||||
'storage_expiration' => env('CLOCKWORK_STORAGE_EXPIRATION', 60 * 24 * 7),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Authentication
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can be configured to require authentication before allowing access to the collected data. This might be
|
||||
| useful when the application is publicly accessible. Setting to true will enable a simple authentication with a
|
||||
| pre-configured password. You can also pass a class name of a custom implementation.
|
||||
|
|
||||
*/
|
||||
|
||||
'authentication' => env('CLOCKWORK_AUTHENTICATION', false),
|
||||
|
||||
// Password for the simple authentication
|
||||
'authentication_password' => env('CLOCKWORK_AUTHENTICATION_PASSWORD', 'VerySecretPassword'),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Stack traces collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can collect stack traces for log messages and certain data like database queries. Here you can set
|
||||
| whether to collect stack traces, limit the number of collected frames and set further configuration. Collecting
|
||||
| long stack traces considerably increases metadata size.
|
||||
|
|
||||
*/
|
||||
|
||||
'stack_traces' => [
|
||||
// Enable or disable collecting of stack traces
|
||||
'enabled' => env('CLOCKWORK_STACK_TRACES_ENABLED', true),
|
||||
|
||||
// Limit the number of frames to be collected
|
||||
'limit' => env('CLOCKWORK_STACK_TRACES_LIMIT', 10),
|
||||
|
||||
// List of vendor names to skip when determining caller, common vendors are automatically added
|
||||
'skip_vendors' => [
|
||||
// 'phpunit'
|
||||
],
|
||||
|
||||
// List of namespaces to skip when determining caller
|
||||
'skip_namespaces' => [
|
||||
// 'Laravel'
|
||||
],
|
||||
|
||||
// List of class names to skip when determining caller
|
||||
'skip_classes' => [
|
||||
// App\CustomLog::class
|
||||
]
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Serialization
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork serializes the collected data to json for storage and transfer. Here you can configure certain aspects
|
||||
| of serialization. Serialization has a large effect on the cpu time and memory usage.
|
||||
|
|
||||
*/
|
||||
|
||||
// Maximum depth of serialized multi-level arrays and objects
|
||||
'serialization_depth' => env('CLOCKWORK_SERIALIZATION_DEPTH', 10),
|
||||
|
||||
// A list of classes that will never be serialized (e.g. a common service container class)
|
||||
'serialization_blackbox' => [
|
||||
\Illuminate\Container\Container::class,
|
||||
\Illuminate\Foundation\Application::class,
|
||||
\Laravel\Lumen\Application::class
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Register helpers
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork comes with a "clock" global helper function. You can use this helper to quickly log something and to
|
||||
| access the Clockwork instance.
|
||||
|
|
||||
*/
|
||||
|
||||
'register_helpers' => env('CLOCKWORK_REGISTER_HELPERS', true),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Send headers for AJAX request
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| When trying to collect data, the AJAX method can sometimes fail if it is missing required headers. For example, an
|
||||
| API might require a version number using Accept headers to route the HTTP request to the correct codebase.
|
||||
|
|
||||
*/
|
||||
|
||||
'headers' => [
|
||||
// 'Accept' => 'application/vnd.com.whatever.v1+json',
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Server timing
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork supports the W3C Server Timing specification, which allows for collecting a simple performance metrics
|
||||
| in a cross-browser way. E.g. in Chrome, your app, database and timeline event timings will be shown in the Dev
|
||||
| Tools network tab. This setting specifies the max number of timeline events that will be sent. Setting to false
|
||||
| will disable the feature.
|
||||
|
|
||||
*/
|
||||
|
||||
'server_timing' => env('CLOCKWORK_SERVER_TIMING', 10)
|
||||
|
||||
];
|
17
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/helpers.php
vendored
Normal file
17
vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/helpers.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
if (! function_exists('clock')) {
|
||||
// Log a message to Clockwork, returns Clockwork instance when called with no arguments, first argument otherwise
|
||||
function clock(...$arguments)
|
||||
{
|
||||
if (empty($arguments)) {
|
||||
return app('clockwork');
|
||||
}
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
app('clockwork')->debug($argument);
|
||||
}
|
||||
|
||||
return reset($arguments);
|
||||
}
|
||||
}
|
38
vendor/itsgoingd/clockwork/Clockwork/Support/Lumen/ClockworkMiddleware.php
vendored
Normal file
38
vendor/itsgoingd/clockwork/Clockwork/Support/Lumen/ClockworkMiddleware.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php namespace Clockwork\Support\Lumen;
|
||||
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Laravel\Lumen\Application;
|
||||
|
||||
// Clockwork Lumen middleware
|
||||
class ClockworkMiddleware
|
||||
{
|
||||
// Lumen application instance
|
||||
protected $app;
|
||||
|
||||
// Create a new middleware instance
|
||||
public function __construct(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
// Handle an incoming request
|
||||
public function handle($request, \Closure $next)
|
||||
{
|
||||
$this->app['clockwork']->event('Controller')->begin();
|
||||
|
||||
try {
|
||||
$response = $next($request);
|
||||
} catch (\Exception $e) {
|
||||
$this->app[ExceptionHandler::class]->report($e);
|
||||
$response = $this->app[ExceptionHandler::class]->render($request, $e);
|
||||
}
|
||||
|
||||
return $this->app['clockwork.support']->processRequest($request, $response);
|
||||
}
|
||||
|
||||
// Record the current request after a response is sent
|
||||
public function terminate()
|
||||
{
|
||||
$this->app['clockwork.support']->recordRequest();
|
||||
}
|
||||
}
|
95
vendor/itsgoingd/clockwork/Clockwork/Support/Lumen/ClockworkServiceProvider.php
vendored
Normal file
95
vendor/itsgoingd/clockwork/Clockwork/Support/Lumen/ClockworkServiceProvider.php
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php namespace Clockwork\Support\Lumen;
|
||||
|
||||
use Clockwork\DataSource\LumenDataSource;
|
||||
use Clockwork\Support\Laravel\ClockworkServiceProvider as LaravelServiceProvider;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
// Clockwork Lumen service provider
|
||||
class ClockworkServiceProvider extends LaravelServiceProvider
|
||||
{
|
||||
// Register Clockwork configuration
|
||||
protected function registerConfiguration()
|
||||
{
|
||||
$this->app->configure('clockwork');
|
||||
$this->mergeConfigFrom(__DIR__ . '/../Laravel/config/clockwork.php', 'clockwork');
|
||||
}
|
||||
|
||||
// Register Clockwork components
|
||||
protected function registerClockwork()
|
||||
{
|
||||
parent::registerClockwork();
|
||||
|
||||
$this->app->singleton('clockwork.support', function ($app) {
|
||||
return new ClockworkSupport($app);
|
||||
});
|
||||
|
||||
if ($this->isRunningWithFacades() && ! class_exists('Clockwork')) {
|
||||
class_alias(\Clockwork\Support\Laravel\Facade::class, 'Clockwork');
|
||||
}
|
||||
}
|
||||
|
||||
// Register Clockwork data sources
|
||||
protected function registerDataSources()
|
||||
{
|
||||
parent::registerDataSources();
|
||||
|
||||
$this->app->singleton('clockwork.lumen', function ($app) {
|
||||
return (new LumenDataSource(
|
||||
$app,
|
||||
$app['clockwork.support']->isFeatureEnabled('log'),
|
||||
$app['clockwork.support']->isFeatureEnabled('views'),
|
||||
$app['clockwork.support']->isFeatureEnabled('routes')
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
// Register Clockwork components aliases for type hinting
|
||||
protected function registerAliases()
|
||||
{
|
||||
parent::registerAliases();
|
||||
|
||||
$this->app->alias('clockwork.lumen', LumenDataSource::class);
|
||||
}
|
||||
|
||||
// Register event listeners
|
||||
protected function registerEventListeners()
|
||||
{
|
||||
$this->app['clockwork.support']->addDataSources()->listenToEvents();
|
||||
}
|
||||
|
||||
// Register Clockwork middleware
|
||||
public function registerMiddleware()
|
||||
{
|
||||
$this->app->middleware([ ClockworkMiddleware::class ]);
|
||||
}
|
||||
|
||||
// Register Clockwork REST api routes
|
||||
public function registerRoutes()
|
||||
{
|
||||
$router = isset($this->app->router) ? $this->app->router : $this->app;
|
||||
|
||||
$router->get('/__clockwork/{id:(?:[0-9-]+|latest)}/extended', 'Clockwork\Support\Lumen\Controller@getExtendedData');
|
||||
$router->get('/__clockwork/{id:(?:[0-9-]+|latest)}[/{direction:(?:next|previous)}[/{count:\d+}]]', 'Clockwork\Support\Lumen\Controller@getData');
|
||||
$router->put('/__clockwork/{id}', 'Clockwork\Support\Lumen\Controller@updateData');
|
||||
$router->post('/__clockwork/auth', 'Clockwork\Support\Lumen\Controller@authenticate');
|
||||
}
|
||||
|
||||
// Register Clockwork app routes
|
||||
public function registerWebRoutes()
|
||||
{
|
||||
$router = isset($this->app->router) ? $this->app->router : $this->app;
|
||||
|
||||
$this->app['clockwork.support']->webPaths()->each(function ($path) use ($router) {
|
||||
$router->get("{$path}", 'Clockwork\Support\Lumen\Controller@webRedirect');
|
||||
$router->get("{$path}/app", 'Clockwork\Support\Lumen\Controller@webIndex');
|
||||
$router->get("{$path}/{path:.+}", 'Clockwork\Support\Lumen\Controller@webAsset');
|
||||
});
|
||||
}
|
||||
|
||||
// Check whether we are running with facades enabled
|
||||
protected function isRunningWithFacades()
|
||||
{
|
||||
return Facade::getFacadeApplication() !== null;
|
||||
}
|
||||
}
|
65
vendor/itsgoingd/clockwork/Clockwork/Support/Lumen/ClockworkSupport.php
vendored
Normal file
65
vendor/itsgoingd/clockwork/Clockwork/Support/Lumen/ClockworkSupport.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php namespace Clockwork\Support\Lumen;
|
||||
|
||||
use Clockwork\Support\Laravel\ClockworkSupport as LaravelSupport;
|
||||
|
||||
use Laravel\Lumen\Application;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
// Support class for the Lumen integration
|
||||
class ClockworkSupport extends LaravelSupport
|
||||
{
|
||||
// Lumen application instance
|
||||
protected $app;
|
||||
|
||||
public function __construct(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
// Resolves the framework data source from the container
|
||||
protected function frameworkDataSource()
|
||||
{
|
||||
return $this->app['clockwork.lumen'];
|
||||
}
|
||||
|
||||
// Process an http request and response, resolves the request, sets Clockwork headers and cookies
|
||||
public function process($request, $response)
|
||||
{
|
||||
if (! $response instanceof Response) {
|
||||
$response = new Response((string) $response);
|
||||
}
|
||||
|
||||
return parent::process($request, $response);
|
||||
}
|
||||
|
||||
// Set response on the framework data source
|
||||
protected function setResponse($response)
|
||||
{
|
||||
$this->app['clockwork.lumen']->setResponse($response);
|
||||
}
|
||||
|
||||
// Check whether Clockwork is enabled
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->getConfig('enable')
|
||||
|| $this->getConfig('enable') === null && env('APP_DEBUG', false);
|
||||
}
|
||||
|
||||
// Check whether a particular feature is available
|
||||
public function isFeatureAvailable($feature)
|
||||
{
|
||||
if ($feature == 'database') {
|
||||
return $this->app->bound('db') && $this->app['config']->get('database.default');
|
||||
} elseif ($feature == 'emails') {
|
||||
return $this->app->bound('mailer');
|
||||
} elseif ($feature == 'redis') {
|
||||
return $this->app->bound('redis') && method_exists(\Illuminate\Redis\RedisManager::class, 'enableEvents');
|
||||
} elseif ($feature == 'queue') {
|
||||
return $this->app->bound('queue') && method_exists(\Illuminate\Queue\Queue::class, 'createPayloadUsing');
|
||||
} elseif ($feature == 'xdebug') {
|
||||
return in_array('xdebug', get_loaded_extensions());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
96
vendor/itsgoingd/clockwork/Clockwork/Support/Lumen/Controller.php
vendored
Normal file
96
vendor/itsgoingd/clockwork/Clockwork/Support/Lumen/Controller.php
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php namespace Clockwork\Support\Lumen;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\Support\Lumen\ClockworkSupport;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Lumen\Routing\Controller as LumenController;
|
||||
use Laravel\Telescope\Telescope;
|
||||
|
||||
// Clockwork api and app controller
|
||||
class Controller extends LumenController
|
||||
{
|
||||
// Clockwork and support instances
|
||||
public $clockwork;
|
||||
public $clockworkSupport;
|
||||
|
||||
public function __construct(Clockwork $clockwork, ClockworkSupport $clockworkSupport)
|
||||
{
|
||||
$this->clockwork = $clockwork;
|
||||
$this->clockworkSupport = $clockworkSupport;
|
||||
}
|
||||
|
||||
// Authantication endpoint
|
||||
public function authenticate(Request $request)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
|
||||
$token = $this->clockwork->authenticator()->attempt(
|
||||
$request->only([ 'username', 'password' ])
|
||||
);
|
||||
|
||||
return new JsonResponse([ 'token' => $token ], $token ? 200 : 403);
|
||||
}
|
||||
|
||||
// Metadata retrieving endpoint
|
||||
public function getData(Request $request, $id = null, $direction = null, $count = null)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
|
||||
return $this->clockworkSupport->getData(
|
||||
$id, $direction, $count, $request->only([ 'only', 'except' ])
|
||||
);
|
||||
}
|
||||
|
||||
// Extended metadata retrieving endpoint
|
||||
public function getExtendedData(Request $request, $id = null)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
|
||||
return $this->clockworkSupport->getExtendedData(
|
||||
$id, $request->only([ 'only', 'except' ])
|
||||
);
|
||||
}
|
||||
|
||||
// Metadata updating endpoint
|
||||
public function updateData(Request $request, $id = null)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
|
||||
return $this->clockworkSupport->updateData($id, $request->json()->all());
|
||||
}
|
||||
|
||||
// App index
|
||||
public function webIndex(Request $request)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
|
||||
return $this->clockworkSupport->getWebAsset('index.html');
|
||||
}
|
||||
|
||||
// App assets serving
|
||||
public function webAsset($path)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
|
||||
return $this->clockworkSupport->getWebAsset($path);
|
||||
}
|
||||
|
||||
// App redirect (/clockwork -> /clockwork/app)
|
||||
public function webRedirect(Request $request)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
|
||||
return new RedirectResponse('/' . $request->path() . '/app');
|
||||
}
|
||||
|
||||
// Ensure Clockwork is still enabled at this point and stop Telescope recording if present
|
||||
protected function ensureClockworkIsEnabled()
|
||||
{
|
||||
if (class_exists(Telescope::class)) Telescope::stopRecording();
|
||||
|
||||
if (! $this->clockworkSupport->isEnabled()) abort(404);
|
||||
}
|
||||
}
|
25
vendor/itsgoingd/clockwork/Clockwork/Support/Monolog/Handler/ClockworkHandler.php
vendored
Normal file
25
vendor/itsgoingd/clockwork/Clockwork/Support/Monolog/Handler/ClockworkHandler.php
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php namespace Clockwork\Support\Monolog\Handler;
|
||||
|
||||
use Clockwork\Request\Log as ClockworkLog;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
|
||||
// Stores messages in a Clockwork log instance
|
||||
// DEPRECATED Moved to Clockwork\Support\Monolog\Monolog\ClockworkHandler, will be removed in Clockwork 6
|
||||
class ClockworkHandler extends AbstractProcessingHandler
|
||||
{
|
||||
protected $clockworkLog;
|
||||
|
||||
public function __construct(ClockworkLog $clockworkLog)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->clockworkLog = $clockworkLog;
|
||||
}
|
||||
|
||||
protected function write(array $record)
|
||||
{
|
||||
$this->clockworkLog->log($record['level'], $record['message']);
|
||||
}
|
||||
}
|
24
vendor/itsgoingd/clockwork/Clockwork/Support/Monolog/Monolog/ClockworkHandler.php
vendored
Normal file
24
vendor/itsgoingd/clockwork/Clockwork/Support/Monolog/Monolog/ClockworkHandler.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php namespace Clockwork\Support\Monolog\Monolog;
|
||||
|
||||
use Clockwork\Request\Log as ClockworkLog;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
|
||||
// Stores messages in a Clockwork log instance (compatible with Monolog 1.x)
|
||||
class ClockworkHandler extends AbstractProcessingHandler
|
||||
{
|
||||
protected $clockworkLog;
|
||||
|
||||
public function __construct(ClockworkLog $clockworkLog)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->clockworkLog = $clockworkLog;
|
||||
}
|
||||
|
||||
protected function write(array $record)
|
||||
{
|
||||
$this->clockworkLog->log($record['level'], $record['message']);
|
||||
}
|
||||
}
|
24
vendor/itsgoingd/clockwork/Clockwork/Support/Monolog/Monolog2/ClockworkHandler.php
vendored
Normal file
24
vendor/itsgoingd/clockwork/Clockwork/Support/Monolog/Monolog2/ClockworkHandler.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php namespace Clockwork\Support\Monolog\Monolog2;
|
||||
|
||||
use Clockwork\Request\Log as ClockworkLog;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
|
||||
// Stores messages in a Clockwork log instance (compatible with Monolog 2.x)
|
||||
class ClockworkHandler extends AbstractProcessingHandler
|
||||
{
|
||||
protected $clockworkLog;
|
||||
|
||||
public function __construct(ClockworkLog $clockworkLog)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->clockworkLog = $clockworkLog;
|
||||
}
|
||||
|
||||
protected function write(array $record): void
|
||||
{
|
||||
$this->clockworkLog->log($record['level'], $record['message']);
|
||||
}
|
||||
}
|
122
vendor/itsgoingd/clockwork/Clockwork/Support/Slim/ClockworkMiddleware.php
vendored
Normal file
122
vendor/itsgoingd/clockwork/Clockwork/Support/Slim/ClockworkMiddleware.php
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php namespace Clockwork\Support\Slim;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\Authentication\NullAuthenticator;
|
||||
use Clockwork\DataSource\PsrMessageDataSource;
|
||||
use Clockwork\Storage\FileStorage;
|
||||
use Clockwork\Helpers\ServerTiming;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
|
||||
// Slim 4 middleware
|
||||
class ClockworkMiddleware
|
||||
{
|
||||
protected $app;
|
||||
protected $clockwork;
|
||||
protected $startTime;
|
||||
|
||||
public function __construct($app, $storagePathOrClockwork, $startTime = null)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->clockwork = $storagePathOrClockwork instanceof Clockwork
|
||||
? $storagePathOrClockwork : $this->createDefaultClockwork($storagePathOrClockwork);
|
||||
$this->startTime = $startTime ?: microtime(true);
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, RequestHandler $handler)
|
||||
{
|
||||
return $this->process($request, $handler);
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler)
|
||||
{
|
||||
$authUri = '#/__clockwork/auth#';
|
||||
if (preg_match($authUri, $request->getUri()->getPath(), $matches)) {
|
||||
return $this->authenticate($request);
|
||||
}
|
||||
|
||||
$clockworkDataUri = '#/__clockwork(?:/(?<id>[0-9-]+))?(?:/(?<direction>(?:previous|next)))?(?:/(?<count>\d+))?#';
|
||||
if (preg_match($clockworkDataUri, $request->getUri()->getPath(), $matches)) {
|
||||
$matches = array_merge([ 'id' => null, 'direction' => null, 'count' => null ], $matches);
|
||||
return $this->retrieveRequest($request, $matches['id'], $matches['direction'], $matches['count']);
|
||||
}
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $this->logRequest($request, $response);
|
||||
}
|
||||
|
||||
protected function authenticate(Request $request)
|
||||
{
|
||||
$token = $this->clockwork->authenticator()->attempt($request->getParsedBody());
|
||||
|
||||
return $this->jsonResponse([ 'token' => $token ], $token ? 200 : 403);
|
||||
}
|
||||
|
||||
protected function retrieveRequest(Request $request, $id, $direction, $count)
|
||||
{
|
||||
$authenticator = $this->clockwork->authenticator();
|
||||
$storage = $this->clockwork->storage();
|
||||
|
||||
$authenticated = $authenticator->check(current($request->getHeader('X-Clockwork-Auth')));
|
||||
|
||||
if ($authenticated !== true) {
|
||||
return $this->jsonResponse([ 'message' => $authenticated, 'requires' => $authenticator->requires() ], 403);
|
||||
}
|
||||
|
||||
if ($direction == 'previous') {
|
||||
$data = $storage->previous($id, $count);
|
||||
} elseif ($direction == 'next') {
|
||||
$data = $storage->next($id, $count);
|
||||
} elseif ($id == 'latest') {
|
||||
$data = $storage->latest();
|
||||
} else {
|
||||
$data = $storage->find($id);
|
||||
}
|
||||
|
||||
return $this->jsonResponse($data);
|
||||
}
|
||||
|
||||
protected function logRequest(Request $request, $response)
|
||||
{
|
||||
$this->clockwork->timeline()->finalize($this->startTime);
|
||||
$this->clockwork->addDataSource(new PsrMessageDataSource($request, $response));
|
||||
|
||||
$this->clockwork->resolveRequest();
|
||||
$this->clockwork->storeRequest();
|
||||
|
||||
$clockworkRequest = $this->clockwork->request();
|
||||
|
||||
$response = $response
|
||||
->withHeader('X-Clockwork-Id', $clockworkRequest->id)
|
||||
->withHeader('X-Clockwork-Version', Clockwork::VERSION);
|
||||
|
||||
if ($basePath = $this->app->getBasePath()) {
|
||||
$response = $response->withHeader('X-Clockwork-Path', $basePath);
|
||||
}
|
||||
|
||||
return $response->withHeader('Server-Timing', ServerTiming::fromRequest($clockworkRequest)->value());
|
||||
}
|
||||
|
||||
protected function createDefaultClockwork($storagePath)
|
||||
{
|
||||
$clockwork = new Clockwork();
|
||||
|
||||
$clockwork->storage(new FileStorage($storagePath));
|
||||
$clockwork->authenticator(new NullAuthenticator);
|
||||
|
||||
return $clockwork;
|
||||
}
|
||||
|
||||
protected function jsonResponse($data, $status = 200)
|
||||
{
|
||||
$response = $this->app->getResponseFactory()
|
||||
->createResponse($status)
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
|
||||
$response->getBody()->write(json_encode($data));
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
113
vendor/itsgoingd/clockwork/Clockwork/Support/Slim/Legacy/ClockworkMiddleware.php
vendored
Normal file
113
vendor/itsgoingd/clockwork/Clockwork/Support/Slim/Legacy/ClockworkMiddleware.php
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php namespace Clockwork\Support\Slim\Legacy;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\Authentication\NullAuthenticator;
|
||||
use Clockwork\DataSource\PsrMessageDataSource;
|
||||
use Clockwork\Storage\FileStorage;
|
||||
use Clockwork\Helpers\ServerTiming;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
// Slim 3 middleware
|
||||
class ClockworkMiddleware
|
||||
{
|
||||
protected $clockwork;
|
||||
protected $startTime;
|
||||
|
||||
public function __construct($storagePathOrClockwork, $startTime = null)
|
||||
{
|
||||
$this->clockwork = $storagePathOrClockwork instanceof Clockwork
|
||||
? $storagePathOrClockwork : $this->createDefaultClockwork($storagePathOrClockwork);
|
||||
$this->startTime = $startTime ?: microtime(true);
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, Response $response, callable $next)
|
||||
{
|
||||
return $this->process($request, $response, $next);
|
||||
}
|
||||
|
||||
public function process(Request $request, Response $response, callable $next)
|
||||
{
|
||||
$authUri = '#/__clockwork/auth#';
|
||||
if (preg_match($authUri, $request->getUri()->getPath(), $matches)) {
|
||||
return $this->authenticate($response, $request);
|
||||
}
|
||||
|
||||
$clockworkDataUri = '#/__clockwork(?:/(?<id>[0-9-]+))?(?:/(?<direction>(?:previous|next)))?(?:/(?<count>\d+))?#';
|
||||
if (preg_match($clockworkDataUri, $request->getUri()->getPath(), $matches)) {
|
||||
$matches = array_merge([ 'id' => null, 'direction' => null, 'count' => null ], $matches);
|
||||
return $this->retrieveRequest($response, $request, $matches['id'], $matches['direction'], $matches['count']);
|
||||
}
|
||||
|
||||
$response = $next($request, $response);
|
||||
|
||||
return $this->logRequest($request, $response);
|
||||
}
|
||||
|
||||
protected function authenticate(Response $response, Request $request)
|
||||
{
|
||||
$token = $this->clockwork->authenticator()->attempt($request->getParsedBody());
|
||||
|
||||
return $response->withJson([ 'token' => $token ])->withStatus($token ? 200 : 403);
|
||||
}
|
||||
|
||||
protected function retrieveRequest(Response $response, Request $request, $id, $direction, $count)
|
||||
{
|
||||
$authenticator = $this->clockwork->authenticator();
|
||||
$storage = $this->clockwork->storage();
|
||||
|
||||
$authenticated = $authenticator->check(current($request->getHeader('X-Clockwork-Auth')));
|
||||
|
||||
if ($authenticated !== true) {
|
||||
return $response
|
||||
->withJson([ 'message' => $authenticated, 'requires' => $authenticator->requires() ])
|
||||
->withStatus(403);
|
||||
}
|
||||
|
||||
if ($direction == 'previous') {
|
||||
$data = $storage->previous($id, $count);
|
||||
} elseif ($direction == 'next') {
|
||||
$data = $storage->next($id, $count);
|
||||
} elseif ($id == 'latest') {
|
||||
$data = $storage->latest();
|
||||
} else {
|
||||
$data = $storage->find($id);
|
||||
}
|
||||
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withJson($data);
|
||||
}
|
||||
|
||||
protected function logRequest(Request $request, Response $response)
|
||||
{
|
||||
$this->clockwork->timeline()->finalize($this->startTime);
|
||||
$this->clockwork->addDataSource(new PsrMessageDataSource($request, $response));
|
||||
|
||||
$this->clockwork->resolveRequest();
|
||||
$this->clockwork->storeRequest();
|
||||
|
||||
$clockworkRequest = $this->clockwork->request();
|
||||
|
||||
$response = $response
|
||||
->withHeader('X-Clockwork-Id', $clockworkRequest->id)
|
||||
->withHeader('X-Clockwork-Version', Clockwork::VERSION);
|
||||
|
||||
if ($basePath = $request->getUri()->getBasePath()) {
|
||||
$response = $response->withHeader('X-Clockwork-Path', $basePath);
|
||||
}
|
||||
|
||||
return $response->withHeader('Server-Timing', ServerTiming::fromRequest($clockworkRequest)->value());
|
||||
}
|
||||
|
||||
protected function createDefaultClockwork($storagePath)
|
||||
{
|
||||
$clockwork = new Clockwork();
|
||||
|
||||
$clockwork->storage(new FileStorage($storagePath));
|
||||
$clockwork->authenticator(new NullAuthenticator);
|
||||
|
||||
return $clockwork;
|
||||
}
|
||||
}
|
42
vendor/itsgoingd/clockwork/Clockwork/Support/Slim/Old/ClockworkLogWriter.php
vendored
Normal file
42
vendor/itsgoingd/clockwork/Clockwork/Support/Slim/Old/ClockworkLogWriter.php
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php namespace Clockwork\Support\Slim\Old;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
|
||||
use Slim\Middleware;
|
||||
|
||||
class ClockworkLogWriter
|
||||
{
|
||||
protected $clockwork;
|
||||
protected $originalLogWriter;
|
||||
|
||||
protected $logLevels = [
|
||||
1 => 'emergency',
|
||||
2 => 'alert',
|
||||
3 => 'critical',
|
||||
4 => 'error',
|
||||
5 => 'warning',
|
||||
6 => 'notice',
|
||||
7 => 'info',
|
||||
8 => 'debug'
|
||||
];
|
||||
|
||||
public function __construct(Clockwork $clockwork, $originalLogWriter)
|
||||
{
|
||||
$this->clockwork = $clockwork;
|
||||
$this->originalLogWriter = $originalLogWriter;
|
||||
}
|
||||
|
||||
public function write($message, $level = null)
|
||||
{
|
||||
$this->clockwork->log($this->getPsrLevel($level), $message);
|
||||
|
||||
if ($this->originalLogWriter) {
|
||||
return $this->originalLogWriter->write($message, $level);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getPsrLevel($level)
|
||||
{
|
||||
return isset($this->logLevels[$level]) ? $this->logLevels[$level] : $level;
|
||||
}
|
||||
}
|
92
vendor/itsgoingd/clockwork/Clockwork/Support/Slim/Old/ClockworkMiddleware.php
vendored
Normal file
92
vendor/itsgoingd/clockwork/Clockwork/Support/Slim/Old/ClockworkMiddleware.php
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php namespace Clockwork\Support\Slim\Old;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\DataSource\PhpDataSource;
|
||||
use Clockwork\DataSource\SlimDataSource;
|
||||
use Clockwork\Helpers\ServerTiming;
|
||||
use Clockwork\Storage\FileStorage;
|
||||
|
||||
use Slim\Middleware;
|
||||
|
||||
// Slim 2 middleware
|
||||
class ClockworkMiddleware extends Middleware
|
||||
{
|
||||
private $storagePathOrClockwork;
|
||||
|
||||
public function __construct($storagePathOrClockwork)
|
||||
{
|
||||
$this->storagePathOrClockwork = $storagePathOrClockwork;
|
||||
}
|
||||
|
||||
public function call()
|
||||
{
|
||||
$this->app->container->singleton('clockwork', function () {
|
||||
if ($this->storagePathOrClockwork instanceof Clockwork) {
|
||||
return $this->storagePathOrClockwork;
|
||||
}
|
||||
|
||||
$clockwork = new Clockwork();
|
||||
|
||||
$clockwork->addDataSource(new PhpDataSource())
|
||||
->addDataSource(new SlimDataSource($this->app))
|
||||
->storage(new FileStorage($this->storagePathOrClockwork));
|
||||
|
||||
return $clockwork;
|
||||
});
|
||||
|
||||
$originalLogWriter = $this->app->getLog()->getWriter();
|
||||
$clockworkLogWriter = new ClockworkLogWriter($this->app->clockwork, $originalLogWriter);
|
||||
|
||||
$this->app->getLog()->setWriter($clockworkLogWriter);
|
||||
|
||||
$clockworkDataUri = '#/__clockwork(?:/(?<id>[0-9-]+))?(?:/(?<direction>(?:previous|next)))?(?:/(?<count>\d+))?#';
|
||||
if ($this->app->config('debug') && preg_match($clockworkDataUri, $this->app->request()->getPathInfo(), $matches)) {
|
||||
$matches = array_merge([ 'direction' => null, 'count' => null ], $matches);
|
||||
return $this->retrieveRequest($matches['id'], $matches['direction'], $matches['count']);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->next->call();
|
||||
$this->logRequest();
|
||||
} catch (Exception $e) {
|
||||
$this->logRequest();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function retrieveRequest($id = null, $direction = null, $count = null)
|
||||
{
|
||||
$storage = $this->app->clockwork->storage();
|
||||
|
||||
if ($direction == 'previous') {
|
||||
$data = $storage->previous($id, $count);
|
||||
} elseif ($direction == 'next') {
|
||||
$data = $storage->next($id, $count);
|
||||
} elseif ($id == 'latest') {
|
||||
$data = $storage->latest();
|
||||
} else {
|
||||
$data = $storage->find($id);
|
||||
}
|
||||
|
||||
echo json_encode($data, \JSON_PARTIAL_OUTPUT_ON_ERROR);
|
||||
}
|
||||
|
||||
protected function logRequest()
|
||||
{
|
||||
$this->app->clockwork->resolveRequest();
|
||||
$this->app->clockwork->storeRequest();
|
||||
|
||||
if ($this->app->config('debug')) {
|
||||
$this->app->response()->header('X-Clockwork-Id', $this->app->clockwork->request()->id);
|
||||
$this->app->response()->header('X-Clockwork-Version', Clockwork::VERSION);
|
||||
|
||||
$env = $this->app->environment();
|
||||
if ($env['SCRIPT_NAME']) {
|
||||
$this->app->response()->header('X-Clockwork-Path', $env['SCRIPT_NAME'] . '/__clockwork/');
|
||||
}
|
||||
|
||||
$request = $this->app->clockwork->request();
|
||||
$this->app->response()->header('Server-Timing', ServerTiming::fromRequest($request)->value());
|
||||
}
|
||||
}
|
||||
}
|
59
vendor/itsgoingd/clockwork/Clockwork/Support/Swift/SwiftPluginClockworkTimeline.php
vendored
Normal file
59
vendor/itsgoingd/clockwork/Clockwork/Support/Swift/SwiftPluginClockworkTimeline.php
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php namespace Clockwork\Support\Swift;
|
||||
|
||||
use Clockwork\Request\Timeline\Timeline as ClockworkTimeline;
|
||||
|
||||
use Swift_Events_SendEvent;
|
||||
use Swift_Events_SendListener;
|
||||
|
||||
// Adds records of sent email to the Clockwork timeline
|
||||
class SwiftPluginClockworkTimeline implements Swift_Events_SendListener
|
||||
{
|
||||
// Clockwork timeline instance
|
||||
protected $timeline;
|
||||
|
||||
public function __construct(ClockworkTimeline $timeline)
|
||||
{
|
||||
$this->timeline = $timeline;
|
||||
}
|
||||
|
||||
// Invoked immediately before a message is sent
|
||||
public function beforeSendPerformed(Swift_Events_SendEvent $evt)
|
||||
{
|
||||
$message = $evt->getMessage();
|
||||
|
||||
$headers = [];
|
||||
foreach ($message->getHeaders()->getAll() as $header) {
|
||||
$headers[$header->getFieldName()] = $header->getFieldBody();
|
||||
}
|
||||
|
||||
$this->timeline->event('Sending an email message', [
|
||||
'name' => 'email ' . $message->getId(),
|
||||
'start' => $time = microtime(true),
|
||||
'data' => [
|
||||
'from' => $this->addressToString($message->getFrom()),
|
||||
'to' => $this->addressToString($message->getTo()),
|
||||
'subject' => $message->getSubject(),
|
||||
'headers' => $headers
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// Invoked immediately after a message is sent
|
||||
public function sendPerformed(Swift_Events_SendEvent $evt)
|
||||
{
|
||||
$message = $evt->getMessage();
|
||||
|
||||
$this->timeline->event('email ' . $message->getId())->end();
|
||||
}
|
||||
|
||||
protected function addressToString($address)
|
||||
{
|
||||
if (! $address) return;
|
||||
|
||||
foreach ($address as $email => $name) {
|
||||
$address[$email] = $name ? "$name <$email>" : $email;
|
||||
}
|
||||
|
||||
return implode(', ', $address);
|
||||
}
|
||||
}
|
11
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkBundle.php
vendored
Normal file
11
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkBundle.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php namespace Clockwork\Support\Symfony;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ClockworkBundle extends Bundle
|
||||
{
|
||||
protected function getContainerExtensionClass()
|
||||
{
|
||||
return ClockworkExtension::class;
|
||||
}
|
||||
}
|
36
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkConfiguration.php
vendored
Normal file
36
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkConfiguration.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php namespace Clockwork\Support\Symfony;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
use Symfony\Component\HttpKernel\Kernel;
|
||||
|
||||
class ClockworkConfiguration implements ConfigurationInterface
|
||||
{
|
||||
protected $debug;
|
||||
|
||||
public function __construct($debug)
|
||||
{
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
public function getConfigTreeBuilder()
|
||||
{
|
||||
return $this->getConfigRoot()
|
||||
->children()
|
||||
->booleanNode('enable')->defaultValue($this->debug)->end()
|
||||
->variableNode('web')->defaultValue(true)->end()
|
||||
->booleanNode('authentication')->defaultValue(false)->end()
|
||||
->scalarNode('authentication_password')->defaultValue('VerySecretPassword')->end()
|
||||
->end()
|
||||
->end();
|
||||
}
|
||||
|
||||
protected function getConfigRoot()
|
||||
{
|
||||
if (Kernel::VERSION_ID < 40300) {
|
||||
return (new TreeBuilder)->root('clockwork');
|
||||
}
|
||||
|
||||
return (new TreeBuilder('clockwork'))->getRootNode();
|
||||
}
|
||||
}
|
69
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkController.php
vendored
Normal file
69
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkController.php
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php namespace Clockwork\Support\Symfony;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class ClockworkController extends AbstractController
|
||||
{
|
||||
protected $clockwork;
|
||||
protected $support;
|
||||
|
||||
public function __construct(Clockwork $clockwork, ClockworkSupport $support)
|
||||
{
|
||||
$this->clockwork = $clockwork;
|
||||
$this->support = $support;
|
||||
}
|
||||
|
||||
public function authenticate(Request $request)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
|
||||
$token = $this->clockwork->authenticator()->attempt($request->request->all());
|
||||
|
||||
return new JsonResponse([ 'token' => $token ], $token ? 200 : 403);
|
||||
}
|
||||
|
||||
public function getData(Request $request, $id = null, $direction = null, $count = null)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
|
||||
return $this->support->getData($request, $id, $direction, $count);
|
||||
}
|
||||
|
||||
public function webIndex(Request $request)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
$this->ensureClockworkWebIsEnabled();
|
||||
|
||||
return $this->support->getWebAsset('index.html');
|
||||
}
|
||||
|
||||
public function webAsset($path)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
$this->ensureClockworkWebIsEnabled();
|
||||
|
||||
return $this->support->getWebAsset($path);
|
||||
}
|
||||
|
||||
public function webRedirect(Request $request)
|
||||
{
|
||||
$this->ensureClockworkIsEnabled();
|
||||
$this->ensureClockworkWebIsEnabled();
|
||||
|
||||
return $this->redirect('/' . $request->getPathInfo() . '/app');
|
||||
}
|
||||
|
||||
protected function ensureClockworkIsEnabled()
|
||||
{
|
||||
if (! $this->support->isEnabled()) throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
protected function ensureClockworkWebIsEnabled()
|
||||
{
|
||||
if (! $this->support->isWebEnabled()) throw $this->createNotFoundException();
|
||||
}
|
||||
}
|
22
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkExtension.php
vendored
Normal file
22
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkExtension.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php namespace Clockwork\Support\Symfony;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
|
||||
|
||||
class ClockworkExtension extends ConfigurableExtension
|
||||
{
|
||||
public function loadInternal(array $config, ContainerBuilder $container)
|
||||
{
|
||||
$loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/Resources/config'));
|
||||
$loader->load('clockwork.php');
|
||||
|
||||
$container->getDefinition(ClockworkSupport::class)->replaceArgument('$config', $config);
|
||||
}
|
||||
|
||||
public function getConfiguration(array $config, ContainerBuilder $container)
|
||||
{
|
||||
return new ClockworkConfiguration($container->getParameter('kernel.debug'));
|
||||
}
|
||||
}
|
38
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkFactory.php
vendored
Normal file
38
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkFactory.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php namespace Clockwork\Support\Symfony;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\Storage\SymfonyStorage;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
class ClockworkFactory
|
||||
{
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function clockwork()
|
||||
{
|
||||
return (new Clockwork)
|
||||
->authenticator($this->container->get('clockwork.authenticator'))
|
||||
->storage($this->container->get('clockwork.storage'));
|
||||
}
|
||||
|
||||
public function clockworkAuthenticator()
|
||||
{
|
||||
return $this->container->get('clockwork.support')->makeAuthenticator();
|
||||
}
|
||||
|
||||
public function clockworkStorage()
|
||||
{
|
||||
return new SymfonyStorage(
|
||||
$this->container->get('profiler'), substr($this->container->getParameter('profiler.storage.dsn'), 5)
|
||||
);
|
||||
}
|
||||
|
||||
public function clockworkSupport($config)
|
||||
{
|
||||
return new ClockworkSupport($this->container, $config);
|
||||
}
|
||||
}
|
47
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkListener.php
vendored
Normal file
47
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkListener.php
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php namespace Clockwork\Support\Symfony;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
||||
use Symfony\Component\HttpKernel\Profiler\Profiler;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class ClockworkListener implements EventSubscriberInterface
|
||||
{
|
||||
protected $clockwork;
|
||||
protected $profiler;
|
||||
|
||||
public function __construct(ClockworkSupport $clockwork, Profiler $profiler)
|
||||
{
|
||||
$this->clockwork = $clockwork;
|
||||
$this->profiler = $profiler;
|
||||
}
|
||||
|
||||
public function onKernelRequest(KernelEvent $event)
|
||||
{
|
||||
if (preg_match('#/__clockwork(.*)#', $event->getRequest()->getPathInfo())) {
|
||||
$this->profiler->disable();
|
||||
}
|
||||
}
|
||||
|
||||
public function onKernelResponse(KernelEvent $event)
|
||||
{
|
||||
if (! $this->clockwork->isEnabled()) return;
|
||||
|
||||
$response = $event->getResponse();
|
||||
|
||||
if (! $response->headers->has('X-Debug-Token')) return;
|
||||
|
||||
$response->headers->set('X-Clockwork-Id', $response->headers->get('X-Debug-Token'));
|
||||
$response->headers->set('X-Clockwork-Version', Clockwork::VERSION);
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
KernelEvents::REQUEST => [ 'onKernelRequest', 512 ],
|
||||
KernelEvents::RESPONSE => [ 'onKernelResponse', -128 ]
|
||||
];
|
||||
}
|
||||
}
|
53
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkLoader.php
vendored
Normal file
53
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkLoader.php
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php namespace Clockwork\Support\Symfony;
|
||||
|
||||
use Symfony\Component\Config\Loader\Loader;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
class ClockworkLoader extends Loader
|
||||
{
|
||||
protected $support;
|
||||
|
||||
public function __construct(ClockworkSupport $support)
|
||||
{
|
||||
$this->support = $support;
|
||||
}
|
||||
|
||||
public function load($resource, $type = null)
|
||||
{
|
||||
$routes = new RouteCollection();
|
||||
|
||||
$routes->add('clockwork', new Route('/__clockwork/{id}/{direction}/{count}', [
|
||||
'_controller' => [ ClockworkController::class, 'getData' ],
|
||||
'direction' => null,
|
||||
'count' => null
|
||||
], [ 'id' => '(?!(app|auth))([a-z0-9-]+|latest)', 'direction' => '(next|previous)', 'count' => '\d+' ]));
|
||||
|
||||
$routes->add('clockwork.auth', new Route('/__clockwork/auth', [
|
||||
'_controller' => [ ClockworkController::class, 'authenticate' ]
|
||||
]));
|
||||
|
||||
if (! $this->support->isWebEnabled()) return $routes;
|
||||
|
||||
foreach ($this->support->webPaths() as $path) {
|
||||
$routes->add("clockwork.webRedirect.{$path}", new Route("{$path}", [
|
||||
'_controller' => [ ClockworkController::class, 'webRedirect' ]
|
||||
]));
|
||||
|
||||
$routes->add("clockwork.webIndex.{$path}", new Route("{$path}/app", [
|
||||
'_controller' => [ ClockworkController::class, 'webIndex' ]
|
||||
]));
|
||||
|
||||
$routes->add("clockwork.webAsset.{$path}", new Route("{$path}/{path}", [
|
||||
'_controller' => [ ClockworkController::class, 'webAsset' ]
|
||||
], [ 'path' => '.+' ]));
|
||||
}
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
public function supports($resource, $type = null)
|
||||
{
|
||||
return $type == 'clockwork';
|
||||
}
|
||||
}
|
100
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkSupport.php
vendored
Normal file
100
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ClockworkSupport.php
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php namespace Clockwork\Support\Symfony;
|
||||
|
||||
use Clockwork\Authentication\NullAuthenticator;
|
||||
use Clockwork\Authentication\SimpleAuthenticator;
|
||||
use Clockwork\Storage\Search;
|
||||
use Clockwork\Web\Web;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class ClockworkSupport
|
||||
{
|
||||
protected $container;
|
||||
protected $config;
|
||||
|
||||
public function __construct(ContainerInterface $container, $config)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getConfig($key, $default = null)
|
||||
{
|
||||
return isset($this->config[$key]) ? $this->config[$key] : $default;
|
||||
}
|
||||
|
||||
public function getData(Request $request, $id = null, $direction = null, $count = null)
|
||||
{
|
||||
$authenticator = $this->container->get('clockwork')->authenticator();
|
||||
$storage = $this->container->get('clockwork')->storage();
|
||||
|
||||
$authenticated = $authenticator->check($request->headers->get('X-Clockwork-Auth'));
|
||||
|
||||
if ($authenticated !== true) {
|
||||
return new JsonResponse([ 'message' => $authenticated, 'requires' => $authenticator->requires() ], 403);
|
||||
}
|
||||
|
||||
if ($direction == 'previous') {
|
||||
$data = $storage->previous($id, $count, Search::fromRequest($request->query->all()));
|
||||
} elseif ($direction == 'next') {
|
||||
$data = $storage->next($id, $count, Search::fromRequest($request->query->all()));
|
||||
} elseif ($id == 'latest') {
|
||||
$data = $storage->latest(Search::fromRequest($request->query->all()));
|
||||
} else {
|
||||
$data = $storage->find($id);
|
||||
}
|
||||
|
||||
$data = is_array($data)
|
||||
? array_map(function ($request) { return $request->toArray(); }, $data)
|
||||
: $data->toArray();
|
||||
|
||||
return new JsonResponse($data);
|
||||
}
|
||||
|
||||
public function getWebAsset($path)
|
||||
{
|
||||
$web = new Web;
|
||||
|
||||
if ($asset = $web->asset($path)) {
|
||||
return new BinaryFileResponse($asset['path'], 200, [ 'Content-Type' => $asset['mime'] ]);
|
||||
} else {
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
}
|
||||
|
||||
public function makeAuthenticator()
|
||||
{
|
||||
$authenticator = $this->getConfig('authentication');
|
||||
|
||||
if (is_string($authenticator)) {
|
||||
return $this->container->get($authenticator);
|
||||
} elseif ($authenticator) {
|
||||
return new SimpleAuthenticator($this->getConfig('authentication_password'));
|
||||
} else {
|
||||
return new NullAuthenticator;
|
||||
}
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->getConfig('enable', false);
|
||||
}
|
||||
|
||||
public function isWebEnabled()
|
||||
{
|
||||
return $this->getConfig('web', true);
|
||||
}
|
||||
|
||||
public function webPaths()
|
||||
{
|
||||
$path = $this->getConfig('web', true);
|
||||
|
||||
if (is_string($path)) return [ trim($path, '/') ];
|
||||
|
||||
return [ 'clockwork', '__clockwork' ];
|
||||
}
|
||||
}
|
303
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ProfileTransformer.php
vendored
Normal file
303
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/ProfileTransformer.php
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
<?php namespace Clockwork\Support\Symfony;
|
||||
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Request\Log;
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Request\Timeline\Timeline;
|
||||
|
||||
use Symfony\Component\HttpKernel\Profiler\Profile;
|
||||
|
||||
class ProfileTransformer
|
||||
{
|
||||
public function transform(Profile $profile)
|
||||
{
|
||||
$request = new Request([ 'id' => $profile->getToken() ]);
|
||||
|
||||
$this->transformCacheData($profile, $request);
|
||||
$this->transformDoctrineData($profile, $request);
|
||||
$this->transformEventsData($profile, $request);
|
||||
$this->transformLoggerData($profile, $request);
|
||||
$this->transformRequestData($profile, $request);
|
||||
$this->transformTimeData($profile, $request);
|
||||
$this->transformTwigData($profile, $request);
|
||||
|
||||
$request->subrequests = $this->getSubrequests($profile);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
// Cache collector
|
||||
|
||||
protected function transformCacheData(Profile $profile, Request $request)
|
||||
{
|
||||
if (! $profile->hasCollector('cache')) return;
|
||||
|
||||
$data = $profile->getCollector('cache');
|
||||
|
||||
$request->cacheQueries = $this->getCacheQueries($data);
|
||||
$request->cacheReads = $data->getTotals()['reads'];
|
||||
$request->cacheHits = $data->getTotals()['hits'];
|
||||
$request->cacheWrites = $data->getTotals()['writes'];
|
||||
$request->cacheDeletes = $data->getTotals()['deletes'];
|
||||
}
|
||||
|
||||
protected function getCacheQueries($data)
|
||||
{
|
||||
return array_reduce(array_map(function ($queries, $connection) {
|
||||
return array_filter(array_map(function ($query) use ($connection) {
|
||||
$value = $query['result'];
|
||||
|
||||
if (! is_array($value) || ! count($value)) return;
|
||||
|
||||
return [
|
||||
'connection' => $connection,
|
||||
'time' => $query['start'],
|
||||
'type' => array_values($value)[0] ? 'hit' : 'miss',
|
||||
'key' => array_keys($value)[0],
|
||||
'value' => '',
|
||||
'duration' => $query['end'] - $query['start']
|
||||
];
|
||||
}, $queries));
|
||||
}, $this->unwrap($data->getCalls()), array_keys($this->unwrap($data->getCalls()))), function ($all, $queries) {
|
||||
return array_merge($all, $queries);
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Doctrine collector
|
||||
|
||||
protected function transformDoctrineData(Profile $profile, Request $request)
|
||||
{
|
||||
if (! $profile->hasCollector('db')) return;
|
||||
|
||||
$data = $profile->getCollector('db');
|
||||
|
||||
$request->databaseDuration = $data->getTime();
|
||||
$request->databaseQueries = $this->getQueries($data);
|
||||
}
|
||||
|
||||
protected function getQueries($data)
|
||||
{
|
||||
return array_reduce(array_map(function ($queries, $connection) {
|
||||
return array_filter(array_map(function ($query) use ($connection) {
|
||||
return [
|
||||
'query' => $this->createRunnableQuery($query['sql'], $this->unwrap($query['params'])),
|
||||
'duration' => $query['executionMS'] * 1000,
|
||||
'connection' => $connection
|
||||
];
|
||||
}, $queries));
|
||||
}, $data->getQueries(), array_keys($data->getQueries())), function ($all, $queries) {
|
||||
return array_merge($all, $queries);
|
||||
}, []);
|
||||
}
|
||||
|
||||
protected function createRunnableQuery($query, $bindings)
|
||||
{
|
||||
foreach ($bindings as $binding) {
|
||||
$binding = \Doctrine\Bundle\DoctrineBundle\Twig\DoctrineExtension::escapeFunction($binding);
|
||||
|
||||
// escape backslashes in the binding (preg_replace requires to do so)
|
||||
$binding = str_replace('\\', '\\\\', $binding);
|
||||
|
||||
$query = preg_replace('/\?/', $binding, $query, 1);
|
||||
}
|
||||
|
||||
// highlight keywords
|
||||
$keywords = [
|
||||
'select', 'insert', 'update', 'delete', 'where', 'from', 'limit', 'is', 'null', 'having', 'group by',
|
||||
'order by', 'asc', 'desc'
|
||||
];
|
||||
$regexp = '/\b' . implode('\b|\b', $keywords) . '\b/i';
|
||||
|
||||
$query = preg_replace_callback($regexp, function ($match) { return strtoupper($match[0]); }, $query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
// Events collector
|
||||
|
||||
protected function transformEventsData(Profile $profile, Request $request)
|
||||
{
|
||||
if (! $profile->hasCollector('events')) return;
|
||||
|
||||
$data = $profile->getCollector('events');
|
||||
|
||||
$request->events = $this->getEvents($data);
|
||||
}
|
||||
|
||||
protected function getEvents($data)
|
||||
{
|
||||
$handledEvents = array_values(array_reduce($this->unwrap($data->getCalledListeners()), function ($events, $listener) {
|
||||
if (! isset($events[$listener['event']])) {
|
||||
$events[$listener['event']] = [ 'event' => $listener['event'], 'listeners' => [] ];
|
||||
}
|
||||
|
||||
$events[$listener['event']]['listeners'][] = $listener['stub'];
|
||||
|
||||
return $events;
|
||||
}, []));
|
||||
|
||||
$orphanedEvents = array_map(function ($event) {
|
||||
return [ 'event' => $event ];
|
||||
}, $this->unwrap($data->getOrphanedEvents()));
|
||||
|
||||
return array_merge($handledEvents, $orphanedEvents);
|
||||
}
|
||||
|
||||
// Log collector
|
||||
|
||||
protected function transformLoggerData(Profile $profile, Request $request)
|
||||
{
|
||||
if (! $profile->hasCollector('logger')) return;
|
||||
|
||||
$data = $profile->getCollector('logger');
|
||||
|
||||
$request->log()->merge($this->getLog($data));
|
||||
}
|
||||
|
||||
protected function getLog($data)
|
||||
{
|
||||
$messages = array_map(function ($log) {
|
||||
$context = isset($log['context']) ? $log['context'] : [];
|
||||
$replacements = array_filter($context, function ($v) { return ! is_array($v) && ! is_object($v) && ! is_resource($v); });
|
||||
|
||||
return [
|
||||
'message' => str_replace(
|
||||
array_map(function ($v) { return "{{$v}}"; }, array_keys($replacements)),
|
||||
array_values($replacements),
|
||||
$log['message']
|
||||
),
|
||||
'context' => (new Serializer)->normalize($log['context']),
|
||||
'level' => strtolower($log['priorityName']),
|
||||
'time' => $log['timestamp']
|
||||
];
|
||||
}, $this->unwrap($data->getLogs()));
|
||||
|
||||
return new Log($messages);
|
||||
}
|
||||
|
||||
// Request collector
|
||||
|
||||
protected function transformRequestData(Profile $profile, Request $request)
|
||||
{
|
||||
if (! $profile->hasCollector('request')) return;
|
||||
|
||||
$data = $profile->getCollector('request');
|
||||
|
||||
$request->method = $data->getMethod();
|
||||
$request->uri = $data->getPathInfo();
|
||||
$request->controller = $this->getController($data);
|
||||
$request->responseStatus = $data->getStatusCode();
|
||||
$request->headers = $this->unwrap($data->getRequestHeaders());
|
||||
$request->getData = $this->unwrap($data->getRequestQuery());
|
||||
$request->postData = $this->unwrap($data->getRequestRequest());
|
||||
$request->cookies = $this->unwrap($data->getRequestCookies());
|
||||
$request->sessionData = (new Serializer)->normalizeEach($this->unwrap($data->getSessionAttributes()));
|
||||
}
|
||||
|
||||
protected function getController($data)
|
||||
{
|
||||
$controller = $this->unwrap($data->getController());
|
||||
|
||||
if (! is_array($controller)) return $controller;
|
||||
|
||||
return isset($controller['method'])
|
||||
? "{$controller['class']}@{$controller['method']}"
|
||||
: $controller['class'];
|
||||
}
|
||||
|
||||
// Time collector
|
||||
|
||||
protected function transformTimeData(Profile $profile, Request $request)
|
||||
{
|
||||
if (! $profile->hasCollector('time')) return;
|
||||
|
||||
$data = $profile->getCollector('time');
|
||||
|
||||
$request->time = $data->getStartTime() / 1000;
|
||||
$request->responseTime = $this->getResponseTime($data);
|
||||
|
||||
$request->timeline()->merge($this->getTimeline($data));
|
||||
}
|
||||
|
||||
protected function getResponseTime($data)
|
||||
{
|
||||
$lastEvent = $data->getEvents()['__section__'];
|
||||
|
||||
return ($lastEvent->getOrigin() + $lastEvent->getDuration()) / 1000;
|
||||
}
|
||||
|
||||
protected function getTimeline($data)
|
||||
{
|
||||
$events = array_map(function ($event, $name) {
|
||||
if ($name == '__section__') {
|
||||
$name = 'Application runtime';
|
||||
} elseif ($name == '__section__.child') {
|
||||
$name = 'Subrequest';
|
||||
}
|
||||
|
||||
return [
|
||||
'start' => ($event->getOrigin() + $event->getStartTime()) / 1000,
|
||||
'end' => ($event->getOrigin() + $event->getEndTime()) / 1000,
|
||||
'duration' => $event->getDuration(),
|
||||
'description' => $name,
|
||||
'data' => []
|
||||
];
|
||||
}, $data->getEvents(), array_keys($data->getEvents()));
|
||||
|
||||
$topEvent = $data->getEvents()['__section__'];
|
||||
array_unshift($events, [
|
||||
'start' => $start = $data->getStartTime() / 1000,
|
||||
'end' => $end = ($topEvent->getOrigin() + $topEvent->getStartTime()) / 1000,
|
||||
'duration' => ($end - $start) * 1000,
|
||||
'description' => 'Symfony initialization',
|
||||
'data' => []
|
||||
]);
|
||||
|
||||
return new Timeline($events);
|
||||
}
|
||||
|
||||
// Twig collector
|
||||
|
||||
protected function transformTwigData(Profile $profile, Request $request)
|
||||
{
|
||||
if (! $profile->hasCollector('twig')) return;
|
||||
|
||||
$data = $profile->getCollector('twig');
|
||||
|
||||
$request->viewsData = $this->getViews($data);
|
||||
}
|
||||
|
||||
protected function getViews($data)
|
||||
{
|
||||
return array_map(function ($template) {
|
||||
return [
|
||||
'description' => 'Rendering a view',
|
||||
'data' => [ 'name' => $template, 'data' => [] ]
|
||||
];
|
||||
}, array_keys($data->getTemplates()));
|
||||
}
|
||||
|
||||
protected function getSubrequests($profile)
|
||||
{
|
||||
return array_map(function ($child) {
|
||||
return [
|
||||
'url' => urlencode($child->getCollector('request')->getPathInfo()),
|
||||
'id' => $child->getToken(),
|
||||
'path' => null
|
||||
];
|
||||
}, $profile->getChildren());
|
||||
}
|
||||
|
||||
protected function unwrap($data)
|
||||
{
|
||||
if ($data instanceof \Symfony\Component\VarDumper\Cloner\Data) {
|
||||
return $data->getValue(true);
|
||||
} elseif ($data instanceof \Symfony\Component\HttpFoundation\ParameterBag) {
|
||||
return array_map(function ($val) { return $val->getValue(); }, $data->all());
|
||||
} elseif (is_array($data)) {
|
||||
return array_map(function ($item) { return $this->unwrap($item); }, $data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
39
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/Resources/config/clockwork.php
vendored
Normal file
39
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/Resources/config/clockwork.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Clockwork\Support\Symfony\ClockworkFactory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
$container->autowire(Clockwork\Support\Symfony\ClockworkFactory::class);
|
||||
|
||||
$container->register(Clockwork\Clockwork::class)
|
||||
->setFactory([ new Reference(ClockworkFactory::class), 'clockwork' ])
|
||||
->setPublic(true);
|
||||
|
||||
$container->register(Clockwork\Authentication\AuthenticatorInterface::class)
|
||||
->setFactory([ new Reference(ClockworkFactory::class), 'clockworkAuthenticator' ])
|
||||
->setPublic(true);
|
||||
|
||||
$container->register(Clockwork\Storage\StorageInterface::class)
|
||||
->setFactory([ new Reference(ClockworkFactory::class), 'clockworkStorage' ])
|
||||
->setPublic(true);
|
||||
|
||||
$container->register(Clockwork\Support\Symfony\ClockworkSupport::class)
|
||||
->setArgument('$config', [])
|
||||
->setFactory([ new Reference(ClockworkFactory::class), 'clockworkSupport' ])
|
||||
->setPublic(true);
|
||||
|
||||
$container->autowire(Clockwork\Support\Symfony\ClockworkController::class)
|
||||
->setAutoconfigured(true);
|
||||
|
||||
$container->autowire(Clockwork\Support\Symfony\ClockworkListener::class)
|
||||
->setArgument('$profiler', new Reference('profiler'))
|
||||
->addTag('kernel.event_subscriber');
|
||||
|
||||
$container->autowire(Clockwork\Support\Symfony\ClockworkLoader::class)
|
||||
->addTag('routing.loader');
|
||||
|
||||
$container->setAlias('clockwork', Clockwork\Clockwork::class)->setPublic('true');
|
||||
$container->setAlias('clockwork.authenticator', Clockwork\Authentication\AuthenticatorInterface::class)->setPublic('true');
|
||||
$container->setAlias('clockwork.storage', Clockwork\Storage\StorageInterface::class)->setPublic('true');
|
||||
$container->setAlias('clockwork.support', Clockwork\Support\Symfony\ClockworkSupport::class)->setPublic('true');
|
7
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/Resources/config/routing/clockwork.php
vendored
Normal file
7
vendor/itsgoingd/clockwork/Clockwork/Support/Symfony/Resources/config/routing/clockwork.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
||||
|
||||
return function (RoutingConfigurator $routes) {
|
||||
$routes->import('.', 'clockwork');
|
||||
};
|
51
vendor/itsgoingd/clockwork/Clockwork/Support/Twig/ProfilerClockworkDumper.php
vendored
Normal file
51
vendor/itsgoingd/clockwork/Clockwork/Support/Twig/ProfilerClockworkDumper.php
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php namespace Clockwork\Support\Twig;
|
||||
|
||||
use Clockwork\Request\Timeline\Timeline;
|
||||
|
||||
use Twig\Profiler\Profile;
|
||||
|
||||
// Converts Twig profiles to a Clockwork rendered views timelines
|
||||
class ProfilerClockworkDumper
|
||||
{
|
||||
protected $lastId = 1;
|
||||
|
||||
// Dumps a profile into a new rendered views timeline
|
||||
public function dump(Profile $profile)
|
||||
{
|
||||
$timeline = new Timeline;
|
||||
|
||||
$this->dumpProfile($profile, $timeline);
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
public function dumpProfile(Profile $profile, Timeline $timeline, $parent = null)
|
||||
{
|
||||
$id = $this->lastId++;
|
||||
|
||||
if ($profile->isRoot()) {
|
||||
$name = $profile->getName();
|
||||
} elseif ($profile->isTemplate()) {
|
||||
$name = basename($profile->getTemplate());
|
||||
} else {
|
||||
$name = basename($profile->getTemplate()) . '::' . $profile->getType() . '(' . $profile->getName() . ')';
|
||||
}
|
||||
|
||||
foreach ($profile as $p) {
|
||||
$this->dumpProfile($p, $timeline, $id);
|
||||
}
|
||||
|
||||
$data = $profile->__serialize();
|
||||
|
||||
$timeline->event($name, [
|
||||
'name' => $id,
|
||||
'start' => isset($data[3]['wt']) ? $data[3]['wt'] : null,
|
||||
'end' => isset($data[4]['wt']) ? $data[4]['wt'] : null,
|
||||
'data' => [
|
||||
'data' => [],
|
||||
'memoryUsage' => isset($data[4]['mu']) ? $data[4]['mu'] : null,
|
||||
'parent' => $parent
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
478
vendor/itsgoingd/clockwork/Clockwork/Support/Vanilla/Clockwork.php
vendored
Normal file
478
vendor/itsgoingd/clockwork/Clockwork/Support/Vanilla/Clockwork.php
vendored
Normal file
@@ -0,0 +1,478 @@
|
||||
<?php namespace Clockwork\Support\Vanilla;
|
||||
|
||||
use Clockwork\Clockwork as BaseClockwork;
|
||||
use Clockwork\Authentication\NullAuthenticator;
|
||||
use Clockwork\Authentication\SimpleAuthenticator;
|
||||
use Clockwork\DataSource\PhpDataSource;
|
||||
use Clockwork\DataSource\PsrMessageDataSource;
|
||||
use Clockwork\Helpers\Serializer;
|
||||
use Clockwork\Helpers\ServerTiming;
|
||||
use Clockwork\Helpers\StackFilter;
|
||||
use Clockwork\Request\IncomingRequest;
|
||||
use Clockwork\Storage\FileStorage;
|
||||
use Clockwork\Storage\Search;
|
||||
use Clockwork\Storage\SqlStorage;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as PsrRequest;
|
||||
use Psr\Http\Message\ResponseInterface as PsrResponse;
|
||||
|
||||
// Clockwork integration for vanilla php and unsupported frameworks
|
||||
class Clockwork
|
||||
{
|
||||
// Clockwork config
|
||||
protected $config;
|
||||
// Clockwork instance
|
||||
protected $clockwork;
|
||||
|
||||
// PSR-7 request and response
|
||||
protected $psrRequest;
|
||||
protected $psrResponse;
|
||||
|
||||
// Whether the headers were already sent (header can be sent manually)
|
||||
protected $headersSent = false;
|
||||
|
||||
// Static instance when used as singleton
|
||||
protected static $defaultInstance;
|
||||
|
||||
// Create new instance, takes an additional config
|
||||
public function __construct($config = [])
|
||||
{
|
||||
$this->config = array_merge(include __DIR__ . '/config.php', $config);
|
||||
|
||||
$this->clockwork = new BaseClockwork;
|
||||
|
||||
$this->clockwork->addDataSource(new PhpDataSource);
|
||||
$this->clockwork->storage($this->makeStorage());
|
||||
$this->clockwork->authenticator($this->makeAuthenticator());
|
||||
|
||||
$this->configureSerializer();
|
||||
$this->configureShouldCollect();
|
||||
$this->configureShouldRecord();
|
||||
|
||||
if ($this->config['register_helpers']) include __DIR__ . '/helpers.php';
|
||||
}
|
||||
|
||||
// Initialize a singleton instance, takes an additional config
|
||||
public static function init($config = [])
|
||||
{
|
||||
return static::$defaultInstance = new static($config);
|
||||
}
|
||||
|
||||
// Return the singleton instance
|
||||
public static function instance()
|
||||
{
|
||||
return static::$defaultInstance;
|
||||
}
|
||||
|
||||
// Resolves and records the current request and sends Clockwork headers, should be called at the end of app
|
||||
// execution, return PSR-7 response if one was set
|
||||
public function requestProcessed()
|
||||
{
|
||||
if (! $this->config['enable'] && ! $this->config['collect_data_always']) return $this->psrResponse;
|
||||
|
||||
if (! $this->clockwork->shouldCollect()->filter($this->incomingRequest())) return $this->psrResponse;
|
||||
if (! $this->clockwork->shouldRecord()->filter($this->clockwork->request())) return $this->psrResponse;
|
||||
|
||||
$this->clockwork->resolveRequest()->storeRequest();
|
||||
|
||||
if (! $this->config['enable']) return $this->psrResponse;
|
||||
|
||||
$this->sendHeaders();
|
||||
|
||||
if (($eventsCount = $this->config['server_timing']) !== false) {
|
||||
$this->setHeader('Server-Timing', ServerTiming::fromRequest($this->clockwork->request(), $eventsCount)->value());
|
||||
}
|
||||
|
||||
return $this->psrResponse;
|
||||
}
|
||||
|
||||
// Resolves and records the current request as a command, should be called at the end of app execution
|
||||
public function commandExecuted($name, $exitCode = null, $arguments = [], $options = [], $argumentsDefaults = [], $optionsDefaults = [], $output = null)
|
||||
{
|
||||
if (! $this->config['enable'] && ! $this->config['collect_data_always']) return;
|
||||
|
||||
if (! $this->clockwork->shouldRecord()->filter($this->clockwork->request())) return;
|
||||
|
||||
$this->clockwork
|
||||
->resolveAsCommand($name, $exitCode, $arguments, $options, $argumentsDefaults, $optionsDefaults, $output)
|
||||
->storeRequest();
|
||||
}
|
||||
|
||||
// Resolves and records the current request as a queue job, should be called at the end of app execution
|
||||
public function queueJobExecuted($name, $description = null, $status = 'processed', $payload = [], $queue = null, $connection = null, $options = [])
|
||||
{
|
||||
if (! $this->config['enable'] && ! $this->config['collect_data_always']) return;
|
||||
|
||||
if (! $this->clockwork->shouldRecord()->filter($this->clockwork->request())) return;
|
||||
|
||||
$this->clockwork
|
||||
->resolveAsQueueJob($name, $description, $status, $payload, $queue, $connection, $options)
|
||||
->storeRequest();
|
||||
}
|
||||
|
||||
// Manually send the Clockwork headers, this should be manually called only when the headers need to be sent early
|
||||
// in the request processing
|
||||
public function sendHeaders()
|
||||
{
|
||||
if (! $this->config['enable'] || $this->headersSent) return;
|
||||
|
||||
$this->headersSent = true;
|
||||
|
||||
$clockworkRequest = $this->request();
|
||||
|
||||
$this->setHeader('X-Clockwork-Id', $clockworkRequest->id);
|
||||
$this->setHeader('X-Clockwork-Version', BaseClockwork::VERSION);
|
||||
|
||||
if ($this->config['api'] != '/__clockwork/') {
|
||||
$this->setHeader('X-Clockwork-Path', $this->config['api']);
|
||||
}
|
||||
|
||||
foreach ($this->config['headers'] as $headerName => $headerValue) {
|
||||
$this->setHeader("X-Clockwork-Header-{$headerName}", $headerValue);
|
||||
}
|
||||
|
||||
if ($this->config['features']['performance']['client_metrics'] || $this->config['toolbar']) {
|
||||
$this->setCookie('x-clockwork', $this->getCookiePayload(), time() + 60);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the x-clockwork cookie payload in case you need to set the cookie yourself (cookie can't be http only,
|
||||
// expiration time should be 60 seconds)
|
||||
public function getCookiePayload()
|
||||
{
|
||||
$clockworkRequest = $this->request();
|
||||
|
||||
return json_encode([
|
||||
'requestId' => $clockworkRequest->id,
|
||||
'version' => BaseClockwork::VERSION,
|
||||
'path' => $this->config['api'],
|
||||
'webPath' => $this->config['web']['enable'],
|
||||
'token' => $clockworkRequest->updateToken,
|
||||
'metrics' => $this->config['features']['performance']['client_metrics'],
|
||||
'toolbar' => $this->config['toolbar']
|
||||
]);
|
||||
}
|
||||
|
||||
// Handle Clockwork REST api request, retrieves or updates Clockwork metadata
|
||||
public function handleMetadata($request = null, $method = null)
|
||||
{
|
||||
if (! $request) $request = isset($_GET['request']) ? $_GET['request'] : '';
|
||||
if (! $method) $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
|
||||
|
||||
if ($method == 'POST' && $request == 'auth') return $this->authenticate();
|
||||
|
||||
return $method == 'POST' ? $this->updateMetadata($request) : $this->returnMetadata($request);
|
||||
}
|
||||
|
||||
// Retrieve metadata based on the passed Clockwork REST api request and send HTTP response
|
||||
public function returnMetadata($request = null)
|
||||
{
|
||||
if (! $this->config['enable']) return $this->response(null, 404);
|
||||
|
||||
$authenticator = $this->clockwork->authenticator();
|
||||
$authenticated = $authenticator->check(isset($_SERVER['HTTP_X_CLOCKWORK_AUTH']) ? $_SERVER['HTTP_X_CLOCKWORK_AUTH'] : '');
|
||||
|
||||
if ($authenticated !== true) {
|
||||
return $this->response([ 'message' => $authenticated, 'requires' => $authenticator->requires() ], 403);
|
||||
}
|
||||
|
||||
return $this->response($this->getMetadata($request));
|
||||
}
|
||||
|
||||
// Returns metadata based on the passed Clockwork REST api request
|
||||
public function getMetadata($request = null)
|
||||
{
|
||||
if (! $this->config['enable']) return;
|
||||
|
||||
$authenticator = $this->clockwork->authenticator();
|
||||
$authenticated = $authenticator->check(isset($_SERVER['HTTP_X_CLOCKWORK_AUTH']) ? $_SERVER['HTTP_X_CLOCKWORK_AUTH'] : '');
|
||||
|
||||
if ($authenticated !== true) return;
|
||||
|
||||
if (! $request) $request = isset($_GET['request']) ? $_GET['request'] : '';
|
||||
|
||||
preg_match('#(?<id>[0-9-]+|latest)(?:/(?<direction>next|previous))?(?:/(?<count>\d+))?#', $request, $matches);
|
||||
|
||||
$id = isset($matches['id']) ? $matches['id'] : null;
|
||||
$direction = isset($matches['direction']) ? $matches['direction'] : null;
|
||||
$count = isset($matches['count']) ? $matches['count'] : null;
|
||||
|
||||
if ($direction == 'previous') {
|
||||
$data = $this->clockwork->storage()->previous($id, $count, Search::fromRequest($_GET));
|
||||
} elseif ($direction == 'next') {
|
||||
$data = $this->clockwork->storage()->next($id, $count, Search::fromRequest($_GET));
|
||||
} elseif ($id == 'latest') {
|
||||
$data = $this->clockwork->storage()->latest(Search::fromRequest($_GET));
|
||||
} else {
|
||||
$data = $this->clockwork->storage()->find($id);
|
||||
}
|
||||
|
||||
if (preg_match('#(?<id>[0-9-]+|latest)/extended#', $request)) {
|
||||
$this->clockwork->extendRequest($data);
|
||||
}
|
||||
|
||||
if ($data) {
|
||||
$data = is_array($data) ? array_map(function ($item) { return $item->toArray(); }, $data) : $data->toArray();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Update metadata based on the passed Clockwork REST api request and send HTTP response
|
||||
public function updateMetadata($request = null)
|
||||
{
|
||||
if (! $this->config['enable'] || ! $this->config['features']['performance']['client_metrics']) {
|
||||
return $this->response(null, 404);
|
||||
}
|
||||
|
||||
if (! $request) $request = isset($_GET['request']) ? $_GET['request'] : '';
|
||||
|
||||
$storage = $this->clockwork->storage();
|
||||
|
||||
$request = $storage->find($request);
|
||||
|
||||
if (! $request) {
|
||||
return $this->response([ 'message' => 'Request not found.' ], 404);
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$token = isset($input['_token']) ? $input['_token'] : '';
|
||||
|
||||
if (! $request->updateToken || ! hash_equals($request->updateToken, $token)) {
|
||||
return $this->response([ 'message' => 'Invalid update token.' ], 403);
|
||||
}
|
||||
|
||||
foreach ($input as $key => $value) {
|
||||
if (in_array($key, [ 'clientMetrics', 'webVitals' ])) {
|
||||
$request->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$storage->update($request);
|
||||
|
||||
return $this->response();
|
||||
}
|
||||
|
||||
// Authanticates access to Clockwork REST api
|
||||
public function authenticate($request = null)
|
||||
{
|
||||
if (! $this->config['enable']) return;
|
||||
|
||||
if (! $request) $request = isset($_GET['request']) ? $_GET['request'] : '';
|
||||
|
||||
$token = $this->clockwork->authenticator()->attempt([
|
||||
'username' => isset($_POST['username']) ? $_POST['username'] : '',
|
||||
'password' => isset($_POST['password']) ? $_POST['password'] : ''
|
||||
]);
|
||||
|
||||
return $this->response([ 'token' => $token ], $token ? 200 : 403);
|
||||
}
|
||||
|
||||
// Returns the Clockwork Web UI as a HTTP response, installs the Web UI on the first run
|
||||
public function returnWeb()
|
||||
{
|
||||
if (! $this->config['web']['enable']) return;
|
||||
|
||||
$this->installWeb();
|
||||
|
||||
$asset = function ($uri) { return "{$this->config['web']['uri']}/{$uri}"; };
|
||||
$metadataPath = $this->config['api'];
|
||||
$url = $this->config['web']['uri'];
|
||||
|
||||
if (! preg_match('#/index.html$#', $url)) {
|
||||
$url = rtrim($url, '/') . '/index.html';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
include __DIR__ . '/iframe.html.php';
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
return $this->response($html, null, false);
|
||||
}
|
||||
|
||||
// Installs the Web UI by copying the assets to the public directory, no-op if already installed
|
||||
public function installWeb()
|
||||
{
|
||||
$path = $this->config['web']['path'];
|
||||
$source = __DIR__ . '/../../Web/public';
|
||||
|
||||
if (file_exists("{$path}/index.html")) return;
|
||||
|
||||
@mkdir($path, 0755, true);
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $item) {
|
||||
if ($item->isDir()) {
|
||||
mkdir("{$path}/" . $iterator->getSubPathName());
|
||||
} else {
|
||||
copy($item, "{$path}/" . $iterator->getSubPathName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a PSR-7 request and response instances instead of vanilla php HTTP apis
|
||||
public function usePsrMessage(PsrRequest $request, PsrResponse $response = null)
|
||||
{
|
||||
$this->psrRequest = $request;
|
||||
$this->psrResponse = $response;
|
||||
|
||||
$this->clockwork->addDataSource(new PsrMessageDataSource($request, $response));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Make a storage implementation based on user configuration
|
||||
protected function makeStorage()
|
||||
{
|
||||
if ($this->config['storage'] == 'sql') {
|
||||
$database = $this->config['storage_sql_database'];
|
||||
$table = $this->config['storage_sql_table'];
|
||||
|
||||
$storage = new SqlStorage(
|
||||
$this->config['storage_sql_database'],
|
||||
$this->config['storage_sql_table'],
|
||||
$this->config['storage_sql_username'],
|
||||
$this->config['storage_sql_password'],
|
||||
$this->config['storage_expiration']
|
||||
);
|
||||
} else {
|
||||
$storage = new FileStorage(
|
||||
$this->config['storage_files_path'],
|
||||
0700,
|
||||
$this->config['storage_expiration'],
|
||||
$this->config['storage_files_compress']
|
||||
);
|
||||
}
|
||||
|
||||
return $storage;
|
||||
}
|
||||
|
||||
// Make an authenticator implementation based on user configuration
|
||||
protected function makeAuthenticator()
|
||||
{
|
||||
$authenticator = $this->config['authentication'];
|
||||
|
||||
if (is_string($authenticator)) {
|
||||
return new $authenticator;
|
||||
} elseif ($authenticator) {
|
||||
return new SimpleAuthenticator($this->config['authentication_password']);
|
||||
} else {
|
||||
return new NullAuthenticator;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure serializer defaults based on user configuration
|
||||
protected function configureSerializer()
|
||||
{
|
||||
Serializer::defaults([
|
||||
'limit' => $this->config['serialization_depth'],
|
||||
'blackbox' => $this->config['serialization_blackbox'],
|
||||
'traces' => $this->config['stack_traces']['enabled'],
|
||||
'tracesSkip' => StackFilter::make()
|
||||
->isNotVendor(array_merge(
|
||||
$this->config['stack_traces']['skip_vendors'],
|
||||
[ 'itsgoingd', 'laravel', 'illuminate' ]
|
||||
))
|
||||
->isNotNamespace($this->config['stack_traces']['skip_namespaces'])
|
||||
->isNotFunction([ 'call_user_func', 'call_user_func_array' ])
|
||||
->isNotClass($this->config['stack_traces']['skip_classes']),
|
||||
'tracesLimit' => $this->config['stack_traces']['limit']
|
||||
]);
|
||||
}
|
||||
|
||||
// Configure should collect rules based on user configuration
|
||||
public function configureShouldCollect()
|
||||
{
|
||||
$this->clockwork->shouldCollect([
|
||||
'onDemand' => $this->config['requests']['on_demand'],
|
||||
'sample' => $this->config['requests']['sample'],
|
||||
'except' => $this->config['requests']['except'],
|
||||
'only' => $this->config['requests']['only'],
|
||||
'exceptPreflight' => $this->config['requests']['except_preflight']
|
||||
]);
|
||||
|
||||
// don't collect data for Clockwork requests
|
||||
$this->clockwork->shouldCollect()->except(preg_quote(rtrim($this->config['api'], '/'), '#'));
|
||||
}
|
||||
|
||||
// Configure should record rules based on user configuration
|
||||
public function configureShouldRecord()
|
||||
{
|
||||
$this->clockwork->shouldRecord([
|
||||
'errorsOnly' => $this->config['requests']['errors_only'],
|
||||
'slowOnly' => $this->config['requests']['slow_only'] ? $this->config['requests']['slow_threshold'] : false
|
||||
]);
|
||||
}
|
||||
|
||||
// Set a cookie on PSR-7 response or using vanilla php
|
||||
protected function setCookie($name, $value, $expires) {
|
||||
if ($this->psrResponse) {
|
||||
$this->psrResponse = $this->psrResponse->withAddedHeader(
|
||||
'Set-Cookie', "{$name}=" . urlencode($value) . '; expires=' . gmdate('D, d M Y H:i:s T', $expires)
|
||||
);
|
||||
} else {
|
||||
setcookie($name, $value, $expires);
|
||||
}
|
||||
}
|
||||
|
||||
// Set a header on PSR-7 response or using vanilla php
|
||||
protected function setHeader($header, $value)
|
||||
{
|
||||
if ($this->psrResponse) {
|
||||
$this->psrResponse = $this->psrResponse->withHeader($header, $value);
|
||||
} else {
|
||||
header("{$header}: {$value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Send a json response, uses the PSR-7 response if set
|
||||
protected function response($data = null, $status = null, $json = true)
|
||||
{
|
||||
if ($json) $this->setHeader('Content-Type', 'application/json');
|
||||
|
||||
if ($this->psrResponse) {
|
||||
if ($status) $this->psrResponse = $this->psrResponse->withStatus($status);
|
||||
$this->psrResponse->getBody()->write($json ? json_encode($data, \JSON_PARTIAL_OUTPUT_ON_ERROR) : $data);
|
||||
return $this->psrResponse;
|
||||
} else {
|
||||
if ($status) http_response_code($status);
|
||||
echo $json ? json_encode($data, \JSON_PARTIAL_OUTPUT_ON_ERROR) : $data;
|
||||
}
|
||||
}
|
||||
|
||||
// Make a Clockwork incoming request instance
|
||||
protected function incomingRequest()
|
||||
{
|
||||
return new IncomingRequest([
|
||||
'method' => $_SERVER['REQUEST_METHOD'],
|
||||
'uri' => $_SERVER['REQUEST_URI'],
|
||||
'input' => $_REQUEST,
|
||||
'cookies' => $_COOKIE
|
||||
]);
|
||||
}
|
||||
|
||||
// Return the underlying Clockwork instance
|
||||
public function getClockwork()
|
||||
{
|
||||
return $this->clockwork;
|
||||
}
|
||||
|
||||
// Pass any method calls to the underlying Clockwork instance
|
||||
public function __call($method, $args = [])
|
||||
{
|
||||
return $this->clockwork->$method(...$args);
|
||||
}
|
||||
|
||||
// Pass any static method calls to the underlying Clockwork instance
|
||||
public static function __callStatic($method, $args = [])
|
||||
{
|
||||
return static::instance()->$method(...$args);
|
||||
}
|
||||
}
|
274
vendor/itsgoingd/clockwork/Clockwork/Support/Vanilla/config.php
vendored
Normal file
274
vendor/itsgoingd/clockwork/Clockwork/Support/Vanilla/config.php
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable Clockwork
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| You can explicitly enable or disable Clockwork here. When disabled,
|
||||
| the storeRequest and returnRequest methods will be no-ops. This provides
|
||||
| a convenient way to disable Clockwork in production.
|
||||
|
|
||||
*/
|
||||
|
||||
'enable' => isset($_ENV['CLOCKWORK_ENABLE']) ? $_ENV['CLOCKWORK_ENABLE'] : true,
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Features
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| You can enable or disable various Clockwork features here. Some features have additional settings (eg. slow query
|
||||
| threshold for database queries).
|
||||
|
|
||||
*/
|
||||
|
||||
'features' => [
|
||||
|
||||
// Performance metrics
|
||||
'performance' => [
|
||||
// Allow collecting of client metrics. Requires separate clockwork-browser npm package.
|
||||
'client_metrics' => isset($_ENV['CLOCKWORK_PERFORMANCE_CLIENT_METRICS']) ? $_ENV['CLOCKWORK_PERFORMANCE_CLIENT_METRICS'] : true
|
||||
]
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable toolbar
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can show a toolbar with basic metrics on all responses. Here you can enable or disable this feature.
|
||||
| Requires a separate clockwork-browser npm library.
|
||||
|
|
||||
*/
|
||||
|
||||
'toolbar' => isset($_ENV['CLOCKWORK_TOOLBAR']) ? $_ENV['CLOCKWORK_TOOLBAR'] : true,
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| HTTP requests collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork collects data about HTTP requests to your app. Here you can choose which requests should be collected.
|
||||
|
|
||||
*/
|
||||
|
||||
'requests' => [
|
||||
// With on-demand mode enabled, Clockwork will only profile requests when the browser extension is open or you
|
||||
// manually pass a "clockwork-profile" cookie or get/post data key.
|
||||
// Optionally you can specify a "secret" that has to be passed as the value to enable profiling.
|
||||
'on_demand' => isset($_ENV['CLOCKWORK_REQUESTS_ON_DEMAND']) ? $_ENV['CLOCKWORK_REQUESTS_ON_DEMAND'] : false,
|
||||
|
||||
// Collect only errors (requests with HTTP 4xx and 5xx responses)
|
||||
'errors_only' => isset($_ENV['CLOCKWORK_REQUESTS_ERRORS_ONLY']) ? $_ENV['CLOCKWORK_REQUESTS_ERRORS_ONLY'] : false,
|
||||
|
||||
// Response time threshold in milliseconds after which the request will be marked as slow
|
||||
'slow_threshold' => isset($_ENV['CLOCKWORK_REQUESTS_SLOW_THRESHOLD']) ? $_ENV['CLOCKWORK_REQUESTS_SLOW_THRESHOLD'] : null,
|
||||
|
||||
// Collect only slow requests
|
||||
'slow_only' => isset($_ENV['CLOCKWORK_REQUESTS_SLOW_ONLY']) ? $_ENV['CLOCKWORK_REQUESTS_SLOW_ONLY'] : false,
|
||||
|
||||
// Sample the collected requests (eg. set to 100 to collect only 1 in 100 requests)
|
||||
'sample' => isset($_ENV['CLOCKWORK_REQUESTS_SAMPLE']) ? $_ENV['CLOCKWORK_REQUESTS_SAMPLE'] : false,
|
||||
|
||||
// List of URIs that should not be collected
|
||||
'except' => [
|
||||
// '/api/.*'
|
||||
],
|
||||
|
||||
// List of URIs that should be collected, any other URI will not be collected if not empty
|
||||
'only' => [
|
||||
// '/api/.*'
|
||||
],
|
||||
|
||||
// Don't collect OPTIONS requests, mostly used in the CSRF pre-flight requests and are rarely of interest
|
||||
'except_preflight' => isset($_ENV['CLOCKWORK_REQUESTS_EXCEPT_PREFLIGHT']) ? $_ENV['CLOCKWORK_REQUESTS_EXCEPT_PREFLIGHT'] : true
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable data collection when Clockwork is disabled
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| You can enable this setting to collect data even when Clockwork is disabled. Eg. for future analysis.
|
||||
|
|
||||
*/
|
||||
|
||||
'collect_data_always' => isset($_ENV['CLOCKWORK_COLLECT_DATA_ALWAYS']) ? $_ENV['CLOCKWORK_COLLECT_DATA_ALWAYS'] : false,
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Clockwork API URI
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Path of the script calling returnRequest to return Clockwork metadata to the client app. See installation
|
||||
| instructions for details.
|
||||
|
|
||||
*/
|
||||
|
||||
'api' => isset($_ENV['CLOCKWORK_API']) ? $_ENV['CLOCKWORK_API'] : '/__clockwork/',
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Clockwork web UI
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork comes bundled with a full Clockwork App accessible as a Web UI. Here you can enable and configure this
|
||||
| feature.
|
||||
| Clockwork::returnWeb api is used to expose the Web UI in your vanilla app, see the installation instructions for
|
||||
| details.
|
||||
|
|
||||
*/
|
||||
|
||||
'web' => [
|
||||
// Enable or disable the Web UI, set to the public uri where Clockwork Web UI is accessible
|
||||
'enable' => isset($_ENV['CLOCKWORK_WEB_ENABLE']) ? $_ENV['CLOCKWORK_WEB_ENABLE'] : true,
|
||||
|
||||
// Path where to install the Web UI assets, should be publicly accessible
|
||||
'path' => isset($_ENV['CLOCKWORK_WEB_PATH']) ? $_ENV['CLOCKWORK_WEB_PATH'] : __DIR__ . '/../../../../../public/vendor/clockwork',
|
||||
|
||||
// Public URI where the installed Web UI assets will be accessible
|
||||
'uri' => isset($_ENV['CLOCKWORK_WEB_URI']) ? $_ENV['CLOCKWORK_WEB_URI'] : '/vendor/clockwork'
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Metadata storage
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Configure how is the metadata collected by Clockwork stored. Two options are available:
|
||||
| - files - A simple fast storage implementation storing data in one-per-request files.
|
||||
| - sql - Stores requests in a sql database. Supports MySQL, Postgresql, Sqlite and requires PDO.
|
||||
|
|
||||
*/
|
||||
|
||||
'storage' => isset($_ENV['CLOCKWORK_STORAGE']) ? $_ENV['CLOCKWORK_STORAGE'] : 'files',
|
||||
|
||||
// Path where the Clockwork metadata is stored
|
||||
'storage_files_path' => isset($_ENV['CLOCKWORK_STORAGE_FILES_PATH']) ? $_ENV['CLOCKWORK_STORAGE_FILES_PATH'] : __DIR__ . '/../../../../../../clockwork',
|
||||
|
||||
// Compress the metadata files using gzip, trading a little bit of performance for lower disk usage
|
||||
'storage_files_compress' => isset($_ENV['CLOCKWORK_STORAGE_FILES_COMPRESS']) ? $_ENV['CLOCKWORK_STORAGE_FILES_COMPRESS'] : false,
|
||||
|
||||
// SQL database to use, can be a PDO connection string or a path to a sqlite file
|
||||
'storage_sql_database' => isset($_ENV['CLOCKWORK_STORAGE_SQL_DATABASE']) ? $_ENV['CLOCKWORK_STORAGE_SQL_DATABASE'] : 'sqlite:' . __DIR__ . '/../../../../../clockwork.sqlite',
|
||||
'storage_sql_username' => isset($_ENV['CLOCKWORK_STORAGE_SQL_USERNAME']) ? $_ENV['CLOCKWORK_STORAGE_SQL_USERNAME'] : null,
|
||||
'storage_sql_password' => isset($_ENV['CLOCKWORK_STORAGE_SQL_PASSWORD']) ? $_ENV['CLOCKWORK_STORAGE_SQL_PASSWORD'] : null,
|
||||
|
||||
// SQL table name to use, the table is automatically created and updated when needed
|
||||
'storage_sql_table' => isset($_ENV['CLOCKWORK_STORAGE_SQL_TABLE']) ? $_ENV['CLOCKWORK_STORAGE_SQL_TABLE'] : 'clockwork',
|
||||
|
||||
// Maximum lifetime of collected metadata in minutes, older requests will automatically be deleted, false to disable
|
||||
'storage_expiration' => isset($_ENV['CLOCKWORK_STORAGE_EXPIRATION']) ? $_ENV['CLOCKWORK_STORAGE_EXPIRATION'] : 60 * 24 * 7,
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Authentication
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can be configured to require authentication before allowing access to the collected data. This might be
|
||||
| useful when the application is publicly accessible. Setting to true will enable a simple authentication with a
|
||||
| pre-configured password. You can also pass a class name of a custom implementation.
|
||||
|
|
||||
*/
|
||||
|
||||
'authentication' => isset($_ENV['CLOCKWORK_AUTHENTICATION']) ? $_ENV['CLOCKWORK_AUTHENTICATION'] : false,
|
||||
|
||||
// Password for the simple authentication
|
||||
'authentication_password' => isset($_ENV['CLOCKWORK_AUTHENTICATION_PASSWORD']) ? $_ENV['CLOCKWORK_AUTHENTICATION_PASSWORD'] : 'VerySecretPassword',
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Stack traces collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can collect stack traces for log messages and certain data like database queries. Here you can set
|
||||
| whether to collect stack traces, limit the number of collected frames and set further configuration. Collecting
|
||||
| long stack traces considerably increases metadata size.
|
||||
|
|
||||
*/
|
||||
|
||||
'stack_traces' => [
|
||||
// Enable or disable collecting of stack traces
|
||||
'enabled' => isset($_ENV['CLOCKWORK_STACK_TRACES_ENABLED']) ? $_ENV['CLOCKWORK_STACK_TRACES_ENABLED'] : true,
|
||||
|
||||
// Limit the number of frames to be collected
|
||||
'limit' => isset($_ENV['CLOCKWORK_STACK_TRACES_LIMIT']) ? $_ENV['CLOCKWORK_STACK_TRACES_LIMIT'] : 10,
|
||||
|
||||
// List of vendor names to skip when determining caller, common vendor are automatically added
|
||||
'skip_vendors' => [
|
||||
// 'phpunit'
|
||||
],
|
||||
|
||||
// List of namespaces to skip when determining caller
|
||||
'skip_namespaces' => [
|
||||
// 'Vendor'
|
||||
],
|
||||
|
||||
// List of class names to skip when determining caller
|
||||
'skip_classes' => [
|
||||
// App\CustomLog::class
|
||||
]
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Serialization
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork serializes the collected data to json for storage and transfer. Here you can configure certain aspects
|
||||
| of serialization. Serialization has a large effect on the cpu time and memory usage.
|
||||
|
|
||||
*/
|
||||
|
||||
// Maximum depth of serialized multi-level arrays and objects
|
||||
'serialization_depth' => isset($_ENV['CLOCKWORK_SERIALIZATION_DEPTH']) ? $_ENV['CLOCKWORK_SERIALIZATION_DEPTH'] : 10,
|
||||
|
||||
// A list of classes that will never be serialized (eg. a common service container class)
|
||||
'serialization_blackbox' => [
|
||||
// \App\ServiceContainer::class
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Register helpers
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork comes with a "clock" global helper function. You can use this helper to quickly log something and to
|
||||
| access the Clockwork instance.
|
||||
|
|
||||
*/
|
||||
|
||||
'register_helpers' => isset($_ENV['CLOCKWORK_REGISTER_HELPERS']) ? $_ENV['CLOCKWORK_REGISTER_HELPERS'] : false,
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Send Headers for AJAX request
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| When trying to collect data the AJAX method can sometimes fail if it is missing required headers. For example, an
|
||||
| API might require a version number using Accept headers to route the HTTP request to the correct codebase.
|
||||
|
|
||||
*/
|
||||
|
||||
'headers' => [
|
||||
// 'Accept' => 'application/vnd.com.whatever.v1+json',
|
||||
],
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Server-Timing
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork supports the W3C Server Timing specification, which allows for collecting a simple performance metrics
|
||||
| in a cross-browser way. Eg. in Chrome, your app, database and timeline event timings will be shown in the Dev
|
||||
| Tools network tab. This setting specifies the max number of timeline events that will be sent. Setting to false
|
||||
| will disable the feature.
|
||||
|
|
||||
*/
|
||||
|
||||
'server_timing' => isset($_ENV['CLOCKWORK_SERVER_TIMING']) ? $_ENV['CLOCKWORK_SERVER_TIMING'] : 10
|
||||
|
||||
];
|
19
vendor/itsgoingd/clockwork/Clockwork/Support/Vanilla/helpers.php
vendored
Normal file
19
vendor/itsgoingd/clockwork/Clockwork/Support/Vanilla/helpers.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Clockwork\Support\Vanilla\Clockwork;
|
||||
|
||||
if (! function_exists('clock')) {
|
||||
// Log a message to Clockwork, returns Clockwork instance when called with no arguments, first argument otherwise
|
||||
function clock(...$arguments)
|
||||
{
|
||||
if (empty($arguments)) {
|
||||
return Clockwork::instance();
|
||||
}
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
Clockwork::debug($argument);
|
||||
}
|
||||
|
||||
return reset($arguments);
|
||||
}
|
||||
}
|
36
vendor/itsgoingd/clockwork/Clockwork/Support/Vanilla/iframe.html.php
vendored
Normal file
36
vendor/itsgoingd/clockwork/Clockwork/Support/Vanilla/iframe.html.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Clockwork</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<?= $asset('img/icons/favicon-32x32.png') ?>">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<?= $asset('img/icons/favicon-16x16.png') ?>">
|
||||
<link rel="manifest" href="<?= $asset('manifest.json') ?>">
|
||||
<meta name="theme-color" content="#4DBA87">
|
||||
<meta name="apple-mobile-web-app-capable" content="no">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Clockwork">
|
||||
<link rel="apple-touch-icon" href="<?= $asset('img/icons/apple-touch-icon-152x152.png') ?>">
|
||||
<link rel="mask-icon" href="<?= $asset('img/icons/safari-pinned-tab.svg') ?>" color="#4DBA87">
|
||||
<meta name="msapplication-TileImage" content="<?= $asset('img/icons/msapplication-icon-144x144.png') ?>">
|
||||
<meta name="msapplication-TileColor" content="#000000">
|
||||
|
||||
<style>
|
||||
iframe { position: fixed; top: 0; left: 0; height: 100%; width: 100%; border: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="<?= $url ?>"></iframe>
|
||||
|
||||
<script>
|
||||
let clockworkData = JSON.parse(localStorage.getItem('clockwork') || '{}')
|
||||
|
||||
clockworkData.settings = clockworkData.settings || {}
|
||||
clockworkData.settings.global = clockworkData.settings.global || {}
|
||||
clockworkData.settings.global.metadataPath = '<?= $metadataPath ?>'
|
||||
|
||||
localStorage.setItem('clockwork', JSON.stringify(clockworkData))
|
||||
</script>
|
||||
</html>
|
36
vendor/itsgoingd/clockwork/Clockwork/Web/Web.php
vendored
Normal file
36
vendor/itsgoingd/clockwork/Clockwork/Web/Web.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php namespace Clockwork\Web;
|
||||
|
||||
// Helper class for serving app assets
|
||||
class Web
|
||||
{
|
||||
// Return the absolute path and a mime type of an asset, protects from accessing files outside Clockwork public dir
|
||||
public function asset($path)
|
||||
{
|
||||
$path = $this->resolveAssetPath($path);
|
||||
|
||||
if (! $path) return;
|
||||
|
||||
switch (pathinfo($path, PATHINFO_EXTENSION)) {
|
||||
case 'css': $mime = 'text/css'; break;
|
||||
case 'js': $mime = 'application/javascript'; break;
|
||||
case 'json': $mime = 'application/json'; break;
|
||||
case 'png': $mime = 'image/png'; break;
|
||||
default: $mime = 'text/html'; break;
|
||||
}
|
||||
|
||||
return [
|
||||
'path' => $path,
|
||||
'mime' => $mime
|
||||
];
|
||||
}
|
||||
|
||||
// Resolves absolute path of the asset, protects from accessing files outside Clockwork public dir
|
||||
protected function resolveAssetPath($path)
|
||||
{
|
||||
$publicPath = realpath(__DIR__ . '/public');
|
||||
|
||||
$path = realpath("$publicPath/{$path}");
|
||||
|
||||
return strpos($path, $publicPath) === 0 ? $path : false;
|
||||
}
|
||||
}
|
1
vendor/itsgoingd/clockwork/Clockwork/Web/public/css/app.515e4027.css
vendored
Normal file
1
vendor/itsgoingd/clockwork/Clockwork/Web/public/css/app.515e4027.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/appearance-auto-icon.png
vendored
Normal file
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/appearance-auto-icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 964 B |
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/appearance-dark-icon.png
vendored
Normal file
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/appearance-dark-icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 975 B |
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/appearance-light-icon.png
vendored
Normal file
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/appearance-light-icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 918 B |
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/icons/apple-touch-icon-120x120.png
vendored
Normal file
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/icons/apple-touch-icon-120x120.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/icons/apple-touch-icon-152x152.png
vendored
Normal file
BIN
vendor/itsgoingd/clockwork/Clockwork/Web/public/img/icons/apple-touch-icon-152x152.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user