Skip to content

Commit

Permalink
Add buffer factory, refactor StreamWrapper DI into PhpStreamWrapper, …
Browse files Browse the repository at this point in the history
…remove ServiceLocator
  • Loading branch information
elazar committed Jan 9, 2024
1 parent 37ea2f1 commit 5ec87ec
Show file tree
Hide file tree
Showing 13 changed files with 178 additions and 123 deletions.
33 changes: 33 additions & 0 deletions src/BufferFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Elazar\Flystream;

class BufferFactory implements BufferFactoryInterface
{
private function __construct(
private BufferInterface|string $bufferInstanceOrClassFqcn
) { }

public static function fromInstance(BufferInterface $instance)
{
return new static($instance);
}

public static function fromClass(string $classFqcn)
{
if (!class_exists($classFqcn)) {
throw FlystreamException::bufferClassNotFound($classFqcn);
}
if (!is_a($classFqcn, BufferInterface::class, true)) {
throw FlystreamException::bufferClassMissingInterface($classFqcn);
}
return new static($classFqcn);
}

public function createBuffer(): BufferInterface
{
return $this->bufferInstanceOrClassFqcn instanceof BufferInterface
? clone $this->bufferInstanceOrClassFqcn
: new $this->bufferInstanceOrClassFqcn;
}
}
8 changes: 8 additions & 0 deletions src/BufferFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Elazar\Flystream;

interface BufferFactoryInterface
{
public function createBuffer(): BufferInterface;
}
5 changes: 1 addition & 4 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ public function __construct()
LocalLockRegistry::class => fn() => new LocalLockRegistry,
NullLogger::class => fn() => new NullLogger,
LoggerInterface::class => fn() => $this->get(NullLogger::class),
BufferInterface::class => fn() => $this->get(MemoryBuffer::class),
MemoryBuffer::class => fn() => new MemoryBuffer,
OverflowBuffer::class => fn() => new OverflowBuffer,
FileBuffer::class => fn() => new FileBuffer,
BufferFactoryInterface::class => fn() => BufferFactory::fromClass(MemoryBuffer::class),
];

$this->instances = [];
Expand Down
2 changes: 1 addition & 1 deletion src/FilesystemRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function register(
throw FlystreamException::protocolRegistered($protocol);
}
$this->filesystems[$protocol] = $filesystem;
stream_wrapper_register($protocol, StreamWrapper::class);
stream_wrapper_register($protocol, PhpStreamWrapper::class);
}

public function unregister(
Expand Down
25 changes: 25 additions & 0 deletions src/FlystreamException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class FlystreamException extends \RuntimeException
public const CODE_PROTOCOL_REGISTERED = 1;
public const CODE_PROTOCOL_NOT_REGISTERED = 2;
public const CODE_CONTAINER_ENTRY_NOT_FOUND = 3;
public const CODE_BUFFER_CLASS_NOT_FOUND = 4;
public const CODE_BUFFER_CLASS_MISSING_INTERFACE = 5;

public static function protocolRegistered(string $protocol): self
{
Expand Down Expand Up @@ -42,4 +44,27 @@ public static function containerEntryNotFound(string $id): self
self::CODE_CONTAINER_ENTRY_NOT_FOUND
) extends FlystreamException implements NotFoundExceptionInterface { };
}

public static function bufferClassNotFound(string $bufferClassFqcn): self
{
return new self(
sprintf(
'Specified buffer class not found: %s',
$bufferClassFqcn
),
self::CODE_BUFFER_CLASS_NOT_FOUND
);
}

public static function bufferClassMissingInterface(string $bufferClassFqcn): self
{
return new self(
sprintf(
'Specified buffer class does not implement %s: %s',
BufferInterface::class,
$bufferClassFqcn
),
self::CODE_BUFFER_CLASS_MISSING_INTERFACE
);
}
}
5 changes: 5 additions & 0 deletions src/OverflowBuffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ public function setMaxMemory(int $maxMemory): void
{
$this->maxMemory = $maxMemory;
}

public function getMaxMemory(): ?int
{
return $this->maxMemory;
}
}
27 changes: 27 additions & 0 deletions src/PhpStreamWrapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Elazar\Flystream;

use League\Flysystem\UnixVisibility\VisibilityConverter;
use Psr\Log\LoggerInterface;

class PhpStreamWrapper extends StreamWrapper
{
private static Container $container;

public static function setContainer(Container $container): void
{
static::$container = $container;
}

public function __construct()
{
parent::__construct(
static::$container->get(VisibilityConverter::class),
static::$container->get(FilesystemRegistry::class),
static::$container->get(LockRegistryInterface::class),
static::$container->get(BufferFactoryInterface::class),
static::$container->get(LoggerInterface::class)
);
}
}
38 changes: 0 additions & 38 deletions src/ServiceLocator.php

This file was deleted.

43 changes: 19 additions & 24 deletions src/StreamWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,11 @@
use League\Flysystem\FilesystemOperator;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\UnixVisibility\VisibilityConverter;
use Pimple\Container;
use Psr\Http\Message\StreamInterface;
use Psr\Log\LoggerInterface;
use Throwable;

class StreamWrapper
{
private ?Lock $lock = null;

private ?string $path = null;

private ?string $mode = null;
Expand All @@ -29,11 +25,21 @@ class StreamWrapper
*/
private $read = null;

private ?BufferInterface $buffer = null;

/** @var resource */
public $context;

private ?Lock $lock = null;

private ?BufferInterface $buffer = null;

public function __construct(
private VisibilityConverter $visibilityConverter,
private FilesystemRegistry $filesystemRegistry,
private LockRegistryInterface $lockRegistry,
private BufferFactoryInterface $bufferFactory,
private LoggerInterface $logger,
) { }

public function dir_closedir(): bool
{
$this->log('info', __METHOD__);
Expand Down Expand Up @@ -86,12 +92,11 @@ public function dir_rewinddir(): bool
public function mkdir(string $path, int $mode, int $options): bool
{
$this->log('info', __METHOD__, func_get_args());
$visibility = $this->get(VisibilityConverter::class);
$filesystem = $this->getFilesystem($path);
try {
$config = $this->getConfig($path, [
Config::OPTION_DIRECTORY_VISIBILITY =>
$visibility->inverseForDirectory($mode),
$this->visibilityConverter->inverseForDirectory($mode),
]);
$filesystem->createDirectory($path, $config);
return true;
Expand Down Expand Up @@ -192,8 +197,6 @@ public function stream_lock(int $operation): bool
{
$this->log('info', __METHOD__, func_get_args());

$locks = $this->get(LockRegistryInterface::class);

// For now, ignore non-blocking requests
$operation &= ~LOCK_NB;

Expand All @@ -204,14 +207,14 @@ public function stream_lock(int $operation): bool
? Lock::TYPE_SHARED
: Lock::TYPE_EXCLUSIVE;
$lock = new Lock($this->path, $type);
$result = $locks->acquire($lock);
$result = $this->lockRegistry->acquire($lock);
if ($result) {
$this->lock = $lock;
}
return $result;
}

$result = $locks->release($this->lock);
$result = $this->lockRegistry->release($this->lock);
if ($result) {
$this->lock = null;
}
Expand Down Expand Up @@ -331,7 +334,7 @@ public function stream_write(string $data)
);
}
if ($this->buffer === null) {
$this->buffer = $this->get(BufferInterface::class);
$this->buffer = $this->bufferFactory->createBuffer();
}
return $this->buffer->write($data);
} catch (Throwable $e) {
Expand Down Expand Up @@ -369,13 +372,12 @@ public function url_stat(string $path, int $flags)
$this->log('info', __METHOD__, func_get_args());

$filesystem = $this->getFilesystem($path);
$visibility = $this->get(VisibilityConverter::class);

if (!$filesystem->fileExists($path)) {
return false;
}

$mode = 0100000 | $visibility->forFile(
$mode = 0100000 | $this->visibilityConverter->forFile(
$filesystem->visibility($path)
);
$size = $filesystem->fileSize($path);
Expand Down Expand Up @@ -412,13 +414,7 @@ private function getConfig(string $path, array $overrides = []): array
private function getFilesystem(string $path): FilesystemOperator
{
$protocol = parse_url($path, PHP_URL_SCHEME);
$registry = $this->get(FilesystemRegistry::class);
return $registry->get($protocol);
}

private function get(string $key)
{
return ServiceLocator::get($key);
return $this->filesystemRegistry->get($protocol);
}

private function getDir(string $path): Iterator
Expand Down Expand Up @@ -451,7 +447,6 @@ private function log(
string $message,
array $context = []
): void {
$logger = $this->get(LoggerInterface::class);
$logger->log($level, $message, $context);
$this->logger->log($level, $message, $context);
}
}
38 changes: 38 additions & 0 deletions tests/BufferFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

use Elazar\Flystream\BufferFactory;
use Elazar\Flystream\FlystreamException;
use Elazar\Flystream\OverflowBuffer;

it('creates a buffer from a buffer instance', function () {
$maxMemory = 1024 ** 2;
$instance = new OverflowBuffer($maxMemory);
$factory = BufferFactory::fromInstance($instance);
$buffer = $factory->createBuffer();
expect($buffer)
->toBeInstanceOf(OverflowBuffer::class)
->not->toBe($instance);
expect($buffer->getMaxMemory())->toBe($maxMemory);
});

it('creates a buffer from a class', function () {
$factory = BufferFactory::fromClass(OverflowBuffer::class);
$buffer = $factory->createBuffer();
expect($buffer)->toBeInstanceOf(OverflowBuffer::class);
});

it('does not create a buffer from a nonexistent class', function () {
BufferFactory::fromClass('NonexistentClass');
})->throws(
FlystreamException::class,
null,
FlystreamException::CODE_BUFFER_CLASS_NOT_FOUND
);

it('does not create a buffer from a non-buffer class', function () {
BufferFactory::fromClass('stdClass');
})->throws(
FlystreamException::class,
null,
FlystreamException::CODE_BUFFER_CLASS_MISSING_INTERFACE
);
26 changes: 13 additions & 13 deletions tests/ContainerTest.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<?php

use Elazar\Flystream\BufferInterface;
use Elazar\Flystream\Container;
use Elazar\Flystream\FileBuffer;
use Elazar\Flystream\FlystreamException;
use Elazar\Flystream\MemoryBuffer;
use Elazar\Flystream\PassThruPathNormalizer;
use Elazar\Flystream\StripProtocolPathNormalizer;
use League\Flysystem\PathNormalizer;

it('can iterate, detect, and get default entries', function () {
$container = new Container;
$expectedDependencyCount = 15;
$expectedDependencyCount = 12;
$actualDependencyCount = 0;
foreach ($container as $class => $instance) {
$actualDependencyCount++;
Expand All @@ -25,21 +25,21 @@
it('can override a dependency using a class name', function () {
$container = new Container;

$default = $container->get(BufferInterface::class);
expect($default)->toBeInstanceOf(MemoryBuffer::class);
$default = $container->get(PathNormalizer::class);
expect($default)->toBeInstanceOf(StripProtocolPathNormalizer::class);

$container->set(BufferInterface::class, FileBuffer::class);
$container->set(PathNormalizer::class, PassThruPathNormalizer::class);

$override = $container->get(BufferInterface::class);
expect($override)->toBeInstanceOf(FileBuffer::class);
$override = $container->get(PathNormalizer::class);
expect($override)->toBeInstanceOf(PassThruPathNormalizer::class);
});

it('can override a dependency using an instance', function () {
$container = new Container;

$buffer = new FileBuffer;
$container->set(BufferInterface::class, $buffer);
$override = new PassThruPathNormalizer;
$container->set(PathNormalizer::class, $override);

$override = $container->get(BufferInterface::class);
expect($override)->toBe($buffer);
$actual = $container->get(PathNormalizer::class);
expect($actual)->toBe($override);
});
Loading

0 comments on commit 5ec87ec

Please sign in to comment.