Skip to content

Commit 637a78e

Browse files
committed
feat: added support for output and error streams
1 parent 0105150 commit 637a78e

File tree

4 files changed

+86
-12
lines changed

4 files changed

+86
-12
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ echo (
1515
)->filter('H4sIAAAAAAAAA0tJLEkEAGPz860EAAAA');
1616
```
1717

18+
If you want to process external data, redirect output or get errors, you can use input, output or error streams.
19+
20+
```php
21+
namespace PetrKnap\ExternalFilter;
22+
23+
$errorStream = fopen('php://memory', 'w+');
24+
25+
(new Filter('php'))->filter(
26+
'<?php fwrite(fopen("php://stderr", "w"), "error");',
27+
error: $errorStream,
28+
);
29+
30+
rewind($errorStream);
31+
echo stream_get_contents($errorStream);
32+
fclose($errorStream);
33+
```
34+
1835
---
1936

2037
Run `composer require petrknap/external-filter` to install it.

src/Filter.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,39 @@ public function __construct(
2424

2525
/**
2626
* @param string|resource $input
27+
* @param resource|null $output
28+
* @param resource|null $error
29+
*
30+
* @return ($output is null ? string : null)
2731
*
2832
* @throws Exception\FilterException
2933
*/
30-
public function filter(mixed $input): string
34+
public function filter(mixed $input, mixed $output = null, mixed $error = null): string|null
3135
{
3236
if (!is_string($input) && !is_resource($input)) {
3337
throw new class ('$input must be string|resource') extends InvalidArgumentException implements Exception\FilterException {
3438
};
3539
}
40+
if ($output !== null && !is_resource($output)) {
41+
throw new class ('$output must be resource|null') extends InvalidArgumentException implements Exception\FilterException {
42+
};
43+
}
44+
if ($error !== null && !is_resource($error)) {
45+
throw new class ('$error must be resource|null') extends InvalidArgumentException implements Exception\FilterException {
46+
};
47+
}
3648

3749
$process = $this->startFilter($input);
38-
39-
$process->wait();
50+
$process->wait(static fn (string $type, string $buffer) => match ($type) {
51+
Process::OUT => $output === null or fwrite($output, $buffer),
52+
Process::ERR => $error === null or fwrite($error, $buffer),
53+
default => null,
54+
});
4055
if (!$process->isSuccessful()) {
4156
throw new class ($process) extends ProcessFailedException implements Exception\FilterException {
4257
};
4358
}
44-
45-
return $process->getOutput();
59+
return $output === null ? $process->getOutput() : null;
4660
}
4761

4862
public function pipe(self $to): self

tests/FilterTest.php

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,41 @@ public static function dataFiltersInput(): iterable
3636
yield 'resource(in-memory stream)' => [$inMemoryStream, $helloWorldPhpStdOut];
3737
}
3838

39+
#[DataProvider('dataWritesToStreamsAndReturnsExpectedValue')]
40+
public function testWritesToStreamsAndReturnsExpectedValue(bool $useOutput, bool $useError): void
41+
{
42+
$outputStream = fopen('php://memory', 'w+');
43+
$errorStream = fopen('php://memory', 'w+');
44+
45+
$returned = (new Filter('php'))->filter(
46+
input: '<?php fwrite(fopen("php://stdout", "w"), "output"); fwrite(fopen("php://stderr", "w"), "error");',
47+
output: $useOutput ? $outputStream : null,
48+
error: $useError ? $errorStream : null,
49+
);
50+
rewind($outputStream);
51+
rewind($errorStream);
52+
53+
self::assertSame([
54+
'returned' => $useOutput ? null : 'output',
55+
'output' => $useOutput ? 'output' : '',
56+
'error' => $useError ? 'error' : '',
57+
], [
58+
'returned' => $returned,
59+
'output' => stream_get_contents($outputStream),
60+
'error' => stream_get_contents($errorStream),
61+
]);
62+
}
63+
64+
public static function dataWritesToStreamsAndReturnsExpectedValue(): array
65+
{
66+
return [
67+
'no stream' => [false, false],
68+
'output stream' => [true, false],
69+
'error stream' => [false, true],
70+
'both streams' => [true, true],
71+
];
72+
}
73+
3974
public function testBuildsAndExecutesPipeline(): void
4075
{
4176
$pipeline = (new Filter('gzip'))->pipe(new Filter('base64'))->pipe(new Filter('base64', ['--decode']));
@@ -48,20 +83,27 @@ public function testBuildsAndExecutesPipeline(): void
4883
}
4984

5085
#[DataProvider('dataThrows')]
51-
public function testThrows(string $command, array $options, mixed $input): void
86+
public function testThrows(string $command, array $options, mixed $input, mixed $output, mixed $error): void
5287
{
5388
self::expectException(Exception\FilterException::class);
5489

55-
(new Filter($command, $options))->filter($input);
90+
(new Filter($command, $options))->filter($input, $output, $error);
5691
}
5792

5893
public static function dataThrows(): array
5994
{
95+
$closedStream = fopen('php://memory', 'w');
96+
fclose($closedStream);
6097
return [
61-
'unknown command' => ['unknown', [], ''],
62-
'unknown option' => ['php', ['--unknown'], ''],
63-
'wrong data' => ['php', [], '<?php wrong data'],
64-
'unsupported input' => ['php', [], new stdClass()],
98+
'unknown command' => ['unknown', [], '', null, null],
99+
'unknown option' => ['php', ['--unknown'], '', null, null],
100+
'wrong data' => ['php', [], '<?php wrong data', null, null],
101+
'unsupported input' => ['php', [], new stdClass(), null, null],
102+
'unsupported output' => ['php', [], '', new stdClass(), null],
103+
'unsupported error' => ['php', [], '', null, new stdClass()],
104+
'closed input' => ['php', [], $closedStream, null, null],
105+
'closed output' => ['php', [], '', $closedStream, null],
106+
'closed error' => ['php', [], '', null, $closedStream],
65107
];
66108
}
67109
}

tests/ReadmeTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ public static function getPathToMarkdownFile(): string
2020
public static function getExpectedOutputsOfPhpExamples(): iterable
2121
{
2222
return [
23-
'example' => 'data',
23+
'pipeline' => 'data',
24+
'error-stream' => 'error',
2425
];
2526
}
2627
}

0 commit comments

Comments
 (0)