Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
yunwuxin committed Dec 1, 2024
0 parents commit 0841fea
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea
vendor
.env
.phpunit.result.cache
composer.lock
32 changes: 32 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "topthink/think-dumper",
"description": "Dumper extend for thinkphp",
"license": "Apache-2.0",
"authors": [
{
"name": "yunwuxin",
"email": "[email protected]"
}
],
"autoload": {
"psr-4": {
"think\\dumper\\": "src"
},
"files": [
"src/helper.php"
]
},
"autoload-dev": {
"psr-4": {
"think\\tests\\dumper\\": "tests/"
}
},
"require": {
"php": "^8.0",
"topthink/framework": "^6.0|^8.0",
"symfony/var-dumper": ">=6.3"
},
"require-dev": {
"phpunit/phpunit": "^11.4"
}
}
25 changes: 25 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
verbose="true"
>
<testsuites>
<testsuite name="ThinkPHP Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>
82 changes: 82 additions & 0 deletions src/Connection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace think\dumper;

use Symfony\Component\VarDumper\Cloner\Data;

class Connection
{
private string $host;
private string $token;
private array $contextProviders;

private $handle;

public function __construct(array $contextProviders = [])
{
$this->host = env('DUMPER_HOST', 'https://developer.topthink.com');
$this->token = env('DUMPER_TOKEN');
$this->contextProviders = $contextProviders;
}

public function write(Data $data): bool
{
if (!$this->handle = $this->handle ?: $this->createHandle()) {
return false;
}

$context = ['timestamp' => microtime(true)];
foreach ($this->contextProviders as $name => $provider) {
$context[$name] = $provider->getContext();
}
$context = array_filter($context);
$encodedPayload = base64_encode(serialize([$data, $context])) . "\n";

set_error_handler(fn() => true);
try {
return $this->sendPayload($encodedPayload);
} finally {
restore_error_handler();
}
}

private function sendPayload($payload)
{
curl_setopt($this->handle, CURLOPT_POSTFIELDS, $payload);
$res = curl_exec($this->handle);
if ($res === false) {
curl_close($this->handle);
$this->handle = null;
return false;
}
$code = curl_getinfo($this->handle, CURLINFO_HTTP_CODE);
return $code == 204;
}

private function createHandle()
{
set_error_handler(fn() => true);
try {
$handle = curl_init("{$this->host}/api/thinkphp/dumper/server");
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_HTTPHEADER, [
"Accept: application/json",
"Authorization: Bearer {$this->token}",
]);
curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($handle, CURLOPT_TIMEOUT, 3);
curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 1);
return $handle;
} finally {
restore_error_handler();
}
}

public function __destruct()
{
if ($this->handle) {
curl_close($this->handle);
}
}
}
113 changes: 113 additions & 0 deletions src/Dumper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

namespace think\dumper;

use Exception;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use think\Request;

class Dumper
{
private static $handlers;

public static function dump($var, string $label = null)
{
$token = env('DUMPER_TOKEN');

$format = self::isCli() ? 'cli' : 'html';

if (!empty($token)) {
$format = 'server';
}

return self::getHandler($format)($var, $label);
}

private static function getHandler($format): callable
{
if (empty(self::$handlers[$format])) {
self::$handlers[$format] = self::createHandler($format);
}

return self::$handlers[$format];
}

private static function createHandler($format)
{
$cloner = new VarCloner();
$cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);

switch ($format) {
case 'html' :
$dumper = new HtmlDumper();
break;
case 'cli':
$dumper = new CliDumper();
break;
case 'server' :
$dumper = self::isCli() ? new CliDumper() : new HtmlDumper();
$dumper = new ServerDumper($dumper, self::getDefaultContextProviders());
break;
default:
throw new Exception('Invalid dump format.');
}

if (!$dumper instanceof ServerDumper) {
$dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]);
}

return function ($var, string $label = null) use ($cloner, $dumper) {
$var = $cloner->cloneVar($var);

if (null !== $label) {
$var = $var->withContext(['label' => $label]);
}

$dumper->dump($var);
};
}

private static function getDefaultContextProviders(): array
{
$contextProviders = [];

if (self::isCli()) {
$contextProviders['cli'] = new class implements ContextProviderInterface {
public function getContext(): ?array
{
return [
'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []),
'identifier' => hash('crc32b', $commandLine . $_SERVER['REQUEST_TIME_FLOAT']),
];
}
};
} else {
$contextProviders['request'] = new class implements ContextProviderInterface {
public function getContext(): ?array
{
$request = app(Request::class);
return [
'uri' => $request->getUri(),
'method' => $request->getMethod(),
'identifier' => spl_object_hash($request),
];
}
};
}

$contextProviders['source'] = new SourceContextProvider();

return $contextProviders;
}

private static function isCli()
{
return app()->runningInConsole();
}
}
31 changes: 31 additions & 0 deletions src/ServerDumper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace think\dumper;

use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;

class ServerDumper implements DataDumperInterface
{
protected Connection $connection;

protected ?DataDumperInterface $wrappedDumper;

public function __construct(DataDumperInterface $wrappedDumper = null, array $contextProviders = [])
{
$this->connection = new Connection($contextProviders);
$this->wrappedDumper = $wrappedDumper;
}

/**
* @return string|null
*/
public function dump(Data $data)
{
if (!$this->connection->write($data) && $this->wrappedDumper) {
return $this->wrappedDumper->dump($data);
}

return null;
}
}
28 changes: 28 additions & 0 deletions src/helper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Symfony\Component\VarDumper\Caster\ScalarStub;
use think\dumper\Dumper;

function d(mixed ...$vars): mixed
{
if (!$vars) {
Dumper::dump(new ScalarStub('🐛'));

return null;
}

if (array_key_exists(0, $vars) && 1 === count($vars)) {
Dumper::dump($vars[0]);
$k = 0;
} else {
foreach ($vars as $k => $v) {
Dumper::dump($v, is_int($k) ? 1 + $k : $k);
}
}

if (1 < count($vars)) {
return $vars;
}

return $vars[$k];
}
15 changes: 15 additions & 0 deletions tests/DumperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace think\tests\dumper;

use PHPUnit\Framework\TestCase;
use think\dumper\Dumper;

class DumperTest extends TestCase
{
public function testDump()
{
Dumper::dump('aa', 'cc');
d(['aa' => 'bbb', 'cc' => 'dd']);
}
}
13 changes: 13 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

$app = new \think\App();
$app->config->set([
'default' => 'file',
'stores' => [
'file' => [
'type' => 'File',
],
],
], 'cache');

$app->initialize();

0 comments on commit 0841fea

Please sign in to comment.