Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stream & Composite handlers #95

Merged
merged 44 commits into from Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e58f2b3
Add UDP and Composite handlers
xepozz Sep 4, 2023
258cbb3
Apply fixes from StyleCI
StyleCIBot Sep 4, 2023
744d994
Fix psalm errors
xepozz Sep 4, 2023
ced31cb
Add changelog
xepozz Sep 4, 2023
257d216
Remove default values
xepozz Sep 4, 2023
8d0b155
Rename udp handler to stream handler, add tests
xepozz Sep 8, 2023
54544d7
Apply fixes from StyleCI
StyleCIBot Sep 8, 2023
db6e45a
Fix psalm errors
xepozz Sep 9, 2023
0a6114e
Merge remote-tracking branch 'origin/upd-handler' into upd-handler
xepozz Sep 9, 2023
7e87045
Remove redundant code
xepozz Sep 9, 2023
734d9c5
Apply fixes from StyleCI
StyleCIBot Sep 9, 2023
c5ee52a
Merge remote-tracking branch 'origin/upd-handler' into upd-handler
xepozz Sep 9, 2023
efbd838
Add test for CompositeHandler
xepozz Sep 9, 2023
8e9e9ba
Add test for EchoHandler
xepozz Sep 9, 2023
a3a5c33
Fix rector
xepozz Sep 9, 2023
06b3477
Add more tests
xepozz Sep 9, 2023
af4113b
Add more tests
xepozz Sep 9, 2023
c344ac0
Fix review comments
xepozz Sep 9, 2023
6c10118
Fix psalm
xepozz Sep 9, 2023
7ad6b8c
Fix composer-require-checker
xepozz Sep 9, 2023
36a4317
Apply fixes from StyleCI
StyleCIBot Sep 9, 2023
429af6f
Add php8.2
xepozz Sep 9, 2023
7b35ada
Disable a few tests on Windows
xepozz Sep 9, 2023
0cfe382
Merge remote-tracking branch 'origin/upd-handler' into upd-handler
xepozz Sep 9, 2023
251fc9f
Disable a few tests on Windows
xepozz Sep 9, 2023
51c4a28
Add a changelog entry
xepozz Sep 9, 2023
43862cf
Fix suggestion text
xepozz Sep 9, 2023
b670bcb
Add destructor tests
xepozz Sep 9, 2023
7b56f8a
Separate sockets and streams
xepozz Sep 9, 2023
d2796d0
Fix test for php 8.0
xepozz Sep 9, 2023
319e9d2
Add immutability test
xepozz Sep 9, 2023
e02ca9e
Apply suggestions from code review
xepozz Sep 16, 2023
64c737e
Apply code review suggestions
xepozz Sep 16, 2023
a9f8dff
Remove `readonly` keyword
xepozz Sep 16, 2023
c19d4c4
Support Socket object
xepozz Sep 16, 2023
9f572c2
Fix composer-require-checker
xepozz Sep 16, 2023
9d270f7
Fix psalm
xepozz Sep 16, 2023
14e59f3
Corrections for PR 95 (#96)
arogachev Sep 20, 2023
8f2b075
Fix psalm
xepozz Sep 21, 2023
6445954
Apply suggestions from code review
xepozz Oct 4, 2023
1edd721
Add phpdoc
xepozz Oct 4, 2023
4dec406
Update .github/workflows/mutation.yml
xepozz Oct 4, 2023
eff8f8f
Update readme
xepozz Oct 4, 2023
9fd803d
Merge remote-tracking branch 'origin/upd-handler' into upd-handler
xepozz Oct 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Expand Up @@ -28,4 +28,4 @@ jobs:
os: >-
['ubuntu-latest', 'windows-latest']
php: >-
['8.0', '8.1']
['8.0', '8.1', '8.2']
2 changes: 1 addition & 1 deletion .github/workflows/composer-require-checker.yml
Expand Up @@ -30,4 +30,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.0', '8.1']
['8.0', '8.1', '8.2']
2 changes: 1 addition & 1 deletion .github/workflows/mutation.yml
Expand Up @@ -26,6 +26,6 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.1']
['8.1', '8.2']
xepozz marked this conversation as resolved.
Show resolved Hide resolved
secrets:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
2 changes: 1 addition & 1 deletion .github/workflows/static.yml
Expand Up @@ -28,4 +28,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.0', '8.1']
['8.0', '8.1', '8.2']
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

## 1.7.0 under development

- New #95: Add `StreamHandler` and `CompositeHandler` (@xepozz)
- Enh #94: Add a middleware handler to control dumps' output destination (@xepozz)
- New #94: Add `dump` function (@xepozz)

Expand Down
11 changes: 11 additions & 0 deletions README.md
Expand Up @@ -100,6 +100,17 @@ In the above `asJson()` will give you nicely formatted code. You can remove form

`$depth` argument allows you to set maximum recursion depth.

## Output destination

Choose one of existing classes or create a new one to control the destination where "dumps" will be sent to:
- [EchoHandler](./src/Handler/EchoHandler.php)
- Uses `echo` to write to stdout stream.
- Used by default.
- [StreamHandler](./src/Handler/StreamHandler.php)
- Uses `ext-sockets` to sent dumps encoded with `json_encode` to a UDP socket.
- [CompositeHandler](./src/Handler/CompositeHandler.php)
- Helpful class to sent dumps to multiple handlers in a row, for example `EchoHandler` and `StreamHandler`.

xepozz marked this conversation as resolved.
Show resolved Hide resolved
## Limitations

Current limitations are:
Expand Down
6 changes: 6 additions & 0 deletions composer-require-checker.json
@@ -0,0 +1,6 @@
{
"symbol-whitelist": [
"Socket",
"socket_write"
]
}
3 changes: 3 additions & 0 deletions composer.json
Expand Up @@ -33,6 +33,9 @@
"spatie/phpunit-watcher": "^1.23",
"vimeo/psalm": "^4.30|^5.3"
},
"suggest": {
"ext-sockets": "Send dumps to a server through UDP/TCP protocols"
},
"autoload": {
"psr-4": {
"Yiisoft\\VarDumper\\": "src"
Expand Down
2 changes: 0 additions & 2 deletions rector.php
Expand Up @@ -6,7 +6,6 @@
use Rector\Config\RectorConfig;
use Rector\Php55\Rector\Class_\ClassConstantToSelfClassRector;
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
use Rector\Php56\Rector\FunctionLike\AddDefaultValueForUndefinedVariableRector;
use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector;
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
use Rector\Set\ValueObject\LevelSetList;
Expand All @@ -27,7 +26,6 @@

$rectorConfig->skip([
ClosureToArrowFunctionRector::class,
AddDefaultValueForUndefinedVariableRector::class,
JsonThrowOnErrorRector::class,
StringClassNameToClassConstantRector::class => [
__DIR__ . '/tests/UseStatementParserTest.php',
Expand Down
30 changes: 30 additions & 0 deletions src/Handler/CompositeHandler.php
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\VarDumper\Handler;

use Yiisoft\VarDumper\HandlerInterface;

/**
* CompositeHandler allows to use multiple handlers at once.
xepozz marked this conversation as resolved.
Show resolved Hide resolved
* It iterates over all handlers and calls their {@see HandlerInterface::handle()} method.
* For example, you may use it to output data to both {@see StreamHandler} and {@see HtmlHandler} at once.
xepozz marked this conversation as resolved.
Show resolved Hide resolved
*/
final class CompositeHandler implements HandlerInterface
arogachev marked this conversation as resolved.
Show resolved Hide resolved
{
/**
* @param HandlerInterface[] $handlers
*/
public function __construct(
private array $handlers
) {
}

public function handle(mixed $variable, int $depth, bool $highlight = false): void
{
foreach ($this->handlers as $handler) {
$handler->handle($variable, $depth, $highlight);
}
}
}
136 changes: 136 additions & 0 deletions src/Handler/StreamHandler.php
@@ -0,0 +1,136 @@
<?php

declare(strict_types=1);

namespace Yiisoft\VarDumper\Handler;

use InvalidArgumentException;
use RuntimeException;
use Socket;
use Yiisoft\VarDumper\HandlerInterface;

use function fsockopen;
use function fwrite;
use function get_debug_type;
use function is_resource;
use function is_string;

/**
* Uses stream ({@link https://www.php.net/manual/en/intro.stream.php} for writing variable's data. Requires "sockets"
xepozz marked this conversation as resolved.
Show resolved Hide resolved
* PHP extension when {@see StreamHandler::$uri} is a {@see Socket} instance.
*/
final class StreamHandler implements HandlerInterface
{
/**
* @var callable|null
*/
private mixed $encoder = null;
/**
* @var resource|Socket|null
* @psalm-suppress PropertyNotSetInConstructor
xepozz marked this conversation as resolved.
Show resolved Hide resolved
*/
private mixed $stream = null;

/**
* @var resource|Socket|string
*/
private mixed $uri;

private const SOCKET_PROTOCOLS = ['udp', 'udg', 'tcp', 'unix'];

/**
* @param mixed|resource|string $uri
*/
public function __construct(
mixed $uri = 'udp://127.0.0.1:8890'
) {
if (!is_string($uri) && !is_resource($uri) && !$uri instanceof Socket) {
throw new InvalidArgumentException(
sprintf(
'Argument $uri must be either a string, a resource or a Socket instance, "%s" given.',
get_debug_type($uri)
)
);
}
$this->uri = $uri;
}

public function __destruct()
{
if (!is_string($this->uri) || !is_resource($this->stream)) {
return;
}
fclose($this->stream);
}

/**
* Encodes {@param $variable} with {@see self::$encoder} and sends the result to the stream.
*/
public function handle(mixed $variable, int $depth, bool $highlight = false): void
{
$data = ($this->encoder ?? '\json_encode')($variable);
if (!is_string($data)) {
throw new RuntimeException(
sprintf(
'Encoder must return a string, "%s" returned.',
get_debug_type($data)
)
);
}

if (!is_resource($this->stream) && !$this->stream instanceof Socket) {
$this->initializeStream();
}

if (!$this->writeToStream($data)) {
$this->initializeStream();

if (!$this->writeToStream($data)) {
throw new RuntimeException('Cannot write a stream.');

Check warning on line 89 in src/Handler/StreamHandler.php

View check run for this annotation

Codecov / codecov/patch

src/Handler/StreamHandler.php#L89

Added line #L89 was not covered by tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A test is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's hard to reproduce async behaviour in tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#98

}
}
}

public function withEncoder(callable $encoder): HandlerInterface
xepozz marked this conversation as resolved.
Show resolved Hide resolved
vjik marked this conversation as resolved.
Show resolved Hide resolved
{
$new = clone $this;
$new->encoder = $encoder;
return $new;
}

private function initializeStream(): void
{
if (!is_string($this->uri)) {
$this->stream = $this->uri;
} else {
$uriHasSocketProtocol = false;
foreach (self::SOCKET_PROTOCOLS as $protocol) {
if (str_starts_with($this->uri, "$protocol://")) {
$uriHasSocketProtocol = true;
break;
}
}

$this->stream = $uriHasSocketProtocol ? fsockopen($this->uri) : fopen($this->uri, 'wb+');
}

if (!is_resource($this->stream) && !$this->stream instanceof Socket) {
throw new RuntimeException('Cannot initialize a stream.');
xepozz marked this conversation as resolved.
Show resolved Hide resolved
}
}

private function writeToStream(string $data): bool
xepozz marked this conversation as resolved.
Show resolved Hide resolved
{
if ($this->stream === null) {
return false;

Check warning on line 125 in src/Handler/StreamHandler.php

View check run for this annotation

Codecov / codecov/patch

src/Handler/StreamHandler.php#L125

Added line #L125 was not covered by tests
}

if ($this->stream instanceof Socket) {
socket_write($this->stream, $data, strlen($data));

return true;
}

return @fwrite($this->stream, $data) !== false;
}
}
26 changes: 26 additions & 0 deletions tests/Handler/CompositeHandlerTest.php
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Yiisoft\VarDumper\Tests\Handler;

use PHPUnit\Framework\TestCase;
use Yiisoft\VarDumper\Handler\CompositeHandler;
use Yiisoft\VarDumper\Tests\Support\InMemoryHandler;

final class CompositeHandlerTest extends TestCase
{
public function testComposite(): void
{
$compositeHandler = new CompositeHandler([
$inMemoryHandler1 = new InMemoryHandler(),
$inMemoryHandler2 = new InMemoryHandler(),
]);
$variable = 'test';

$compositeHandler->handle($variable, 1, true);

$this->assertEquals([[$variable, 1, true]], $inMemoryHandler1->getVariables());
$this->assertEquals($inMemoryHandler1->getVariables(), $inMemoryHandler2->getVariables());
arogachev marked this conversation as resolved.
Show resolved Hide resolved
}
}
33 changes: 33 additions & 0 deletions tests/Handler/EchoHandlerTest.php
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Yiisoft\VarDumper\Tests\Handler;

use PHPUnit\Framework\TestCase;
use Yiisoft\VarDumper\Handler\EchoHandler;

final class EchoHandlerTest extends TestCase
{
public function testEcho(): void
{
$handler = new EchoHandler();

$handler->handle('test', 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$handler->handle('test', 1);
$handler->handle('test', depth: 1);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better readability.


$this->expectOutputString("'test'");
}

public function testHighlight(): void
{
$handler = new EchoHandler();

$handler->handle('test', 1, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$handler->handle('test', 1, true);
$handler->handle('test', depth: 1, highlight: true);


$this->expectOutputString(
<<<HTML
<code><span style="color: #000000">\n<span style="color: #0000BB"></span><span style="color: #DD0000">'test'</span>\n</span>\n</code>
HTML
);
}
}