Skip to content

Commit

Permalink
Merge pull request #3 from phpro/common-php-streams
Browse files Browse the repository at this point in the history
Add common PHP I/O stream factories
  • Loading branch information
veewee authored Jan 22, 2025
2 parents 65cb294 + 31efd6f commit 64bca1d
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 0 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ $stream->close();

The following streams are available by default:

### CliStream

Can be used to open up CLI `stdin`, `stdout` and `stderr` streams.

```php
use Phpro\ResourceStream\Factory\CliStream;

$stdin = CliStream::stdin();
$stdout = CliStream::stdout();
$stderr = CliStream::stderr();
```

### FileStream

Validates if the local file exists and opens it up for you to use.
Expand All @@ -66,6 +78,17 @@ use Phpro\ResourceStream\Factory\FileStream;
$stream = FileStream::create('/path/to/file', FileStream::READ_WRITE_MODE);
```

### IOStream

Can be used to open up CGI `php://input` and `php://output` streams.

```php
use Phpro\ResourceStream\Factory\IOStream;

$input = IOStream::input();
$output = IOStream::output();
```

### MemoryStream

Creates an in-memory stream for you to use.
Expand Down
57 changes: 57 additions & 0 deletions src/Factory/CliStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Phpro\ResourceStream\Factory;

use Phpro\ResourceStream\ErrorHandling\SafeStreamAction;
use Phpro\ResourceStream\Exception\RuntimeException;
use Phpro\ResourceStream\ResourceStream;

final class CliStream
{
/**
* @throws RuntimeException
*
* @return ResourceStream<resource>
*/
public static function stdout(): ResourceStream
{
$resource = SafeStreamAction::run(
static fn () => fopen('php://stdout', 'w'),
'Unable to open file "php://stdout"'
);

return new ResourceStream($resource);
}

/**
* @throws RuntimeException
*
* @return ResourceStream<resource>
*/
public static function stdin(): ResourceStream
{
$resource = SafeStreamAction::run(
static fn () => fopen('php://stdin', 'r'),
'Unable to open file "php://stdin"'
);

return new ResourceStream($resource);
}

/**
* @throws RuntimeException
*
* @return ResourceStream<resource>
*/
public static function stderr(): ResourceStream
{
$resource = SafeStreamAction::run(
static fn () => fopen('php://stderr', 'w'),
'Unable to open file "php://stderr"'
);

return new ResourceStream($resource);
}
}
42 changes: 42 additions & 0 deletions src/Factory/IOStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Phpro\ResourceStream\Factory;

use Phpro\ResourceStream\ErrorHandling\SafeStreamAction;
use Phpro\ResourceStream\Exception\RuntimeException;
use Phpro\ResourceStream\ResourceStream;

final class IOStream
{
/**
* @throws RuntimeException
*
* @return ResourceStream<resource>
*/
public static function input(): ResourceStream
{
$resource = SafeStreamAction::run(
static fn () => fopen('php://input', FileStream::READ_MODE),
'Unable to open file "php://input"'
);

return new ResourceStream($resource);
}

/**
* @throws RuntimeException
*
* @return ResourceStream<resource>
*/
public static function output(): ResourceStream
{
$resource = SafeStreamAction::run(
static fn () => fopen('php://output', FileStream::WRITE_MODE),
'Unable to open file "php://output"'
);

return new ResourceStream($resource);
}
}
16 changes: 16 additions & 0 deletions src/ResourceStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ public function __construct(
$this->unwrap();
}

/**
* @param mixed $resource
*
* @psalm-assert resource $resource
*
* @throws RuntimeException
*/
public static function parse(mixed $resource): self
{
if (!is_resource($resource)) {
throw ResourceStreamException::noResource();
}

return new self($resource);
}

public function __destruct()
{
if (!$this->keepAlive) {
Expand Down
41 changes: 41 additions & 0 deletions tests/Unit/Factory/CliStreamTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Unit\Factory;

use Phpro\ResourceStream\Factory\CliStream;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

#[CoversClass(CliStream::class)]
class CliStreamTest extends TestCase
{
#[Test]
public function it_can_open_input_cli_stream(): void
{
$stream = CliStream::stdin();

self::assertTrue($stream->isOpen());
self::assertStringContainsString('php://stdin', $stream->uri());
}

#[Test]
public function it_can_open_output_cli_stream(): void
{
$stream = CliStream::stdout();

self::assertTrue($stream->isOpen());
self::assertStringContainsString('php://stdout', $stream->uri());
}

#[Test]
public function it_can_open_error_cli_stream(): void
{
$stream = CliStream::stderr();

self::assertTrue($stream->isOpen());
self::assertStringContainsString('php://stderr', $stream->uri());
}
}
32 changes: 32 additions & 0 deletions tests/Unit/Factory/IOStreamTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Unit\Factory;

use Phpro\ResourceStream\Factory\IOStream;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

#[CoversClass(IOStream::class)]
class IOStreamTest extends TestCase
{
#[Test]
public function it_can_open_input_io_stream(): void
{
$stream = IOStream::input();

self::assertTrue($stream->isOpen());
self::assertStringContainsString('php://input', $stream->uri());
}

#[Test]
public function it_can_open_output_io_stream(): void
{
$stream = IOStream::output();

self::assertTrue($stream->isOpen());
self::assertStringContainsString('php://output', $stream->uri());
}
}
16 changes: 16 additions & 0 deletions tests/Unit/ResourceStreamTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ public function it_detects_closed_streams_during_construction(): void
new ResourceStream($f);
}

#[Test]
public function it_can_parse_a_valid_stream(): void
{
$f = fopen('php://memory', 'r');
$resourceStream = ResourceStream::parse($f);

self::assertSame($f, $resourceStream->unwrap());
}

#[Test]
public function it_can_parse_a_mixed_value_to_be_a_stream(): void
{
$this->expectException(ResourceStreamException::class);
ResourceStream::parse('no-stream');
}

#[Test]
public function it_can_unwrap_a_stream(): void
{
Expand Down

0 comments on commit 64bca1d

Please sign in to comment.