Skip to content

Commit

Permalink
Working on new component for filesystems (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaEstes authored Oct 27, 2023
0 parents commit 9e1d0cf
Show file tree
Hide file tree
Showing 35 changed files with 1,922 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/Tests export-ignore
/phpunit.xml.dist export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
composer.lock
phpunit.xml
vendor/
60 changes: 60 additions & 0 deletions Adapter/AdapterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Component\Filesystem\Adapter;

use SonsOfPHP\Component\Filesystem\ContextInterface;
use SonsOfPHP\Component\Filesystem\Exception\FilesystemException;
use SonsOfPHP\Component\Filesystem\Exception\FileNotFoundException;

/**
* Base Adapter Interface that all adapters implement.
*
* @author Joshua Estes <[email protected]>
*/
interface AdapterInterface
{
/**
* Adds a file to the filesystem.
*
* @param string|resource $contents
* Can either be a string or a resource. If not one of these types, the
* adapter should throw an exception
*
* @throws FilesystemException When $contents is invalid argument
* @throws FilesystemException Generic Failure Exception
*/
public function add(string $path, mixed $contents, ?ContextInterface $context = null): void;

/**
* Returns the content of a given file found at $path
*
* @throws FileNotFoundException When $path does not exist
* @throws FilesystemException Generic Failure Exception
*
* @return string|resouce
* This should return either a string or resouce
*/
public function get(string $path, ?ContextInterface $context = null): mixed;

/**
* Deletes files and directories
*
* @throws FileNotFouneException When $path does not exist
* @throws FilesystemException Generic Failure Exception
*/
public function remove(string $path, ?ContextInterface $context = null): void;

/**
* Checks to see if a file or directory exists
*
* @throws FilesystemException Generic Failure Exception
*/
public function has(string $path, ?ContextInterface $context = null): bool;

/**
* @throws FilesystemException Generic Failure Exception
*/
public function isFile(string $path, ?ContextInterface $context = null): bool;
}
107 changes: 107 additions & 0 deletions Adapter/ChainAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Component\Filesystem\Adapter;

use SonsOfPHP\Component\Filesystem\ContextInterface;
use SonsOfPHP\Component\Filesystem\Exception\FileNotFoundException;
use SonsOfPHP\Component\Filesystem\Exception\FilesystemException;

/**
* Chain adapter allows you to use multiple adapters together.
*
* Usage:
* $adapter = new ChainAdapter([new InMemoryAdapter(), new NativeAdapter('/tmp')]);
*
* @author Joshua Estes <[email protected]>
*/
final class ChainAdapter implements AdapterInterface, CopyAwareInterface, DirectoryAwareInterface, MoveAwareInterface
{
public function __construct(
private iterable $adapters,
) {}

public function add(string $path, mixed $contents, ?ContextInterface $context = null): void
{
foreach ($this->adapters as $adapter) {
$adapter->add($path, $contents, $context);
}
}

public function get(string $path, ?ContextInterface $context = null): mixed
{
foreach ($this->adapters as $adapter) {
if ($adapter->has($path, $context)) {
return $adapter->get($path, $context);
}
}

throw new FileNotFoundException();
}

public function remove(string $path, ?ContextInterface $context = null): void
{
foreach ($this->adapters as $adapter) {
$adapter->remove($path, $context);
}
}

public function has(string $path, ?ContextInterface $context = null): bool
{
foreach ($this->adapters as $adapter) {
if ($adapter->has($path, $context)) {
return true;
}
}

return false;
}

public function isFile(string $path, ?ContextInterface $context = null): bool
{
foreach ($this->adapters as $adapter) {
if ($adapter->isFile($path, $context)) {
return true;
}
}

return false;
}

public function copy(string $source, string $destination, ?ContextInterface $context = null): void
{
foreach ($this->adapters as $adapter) {
if ($adapter instanceof CopyAwareInterface) {
$adapter->copy($source, $destination, $context);
continue;
}

$adapter->add($destination, $adapter->get($source, $context), $context);
}
}

public function isDirectory(string $path, ?ContextInterface $context = null): bool
{
foreach ($this->adapters as $adapter) {
if ($adapter instanceof DirectoryAwareInterface && $adapter->isDirectory($path, $context)) {
return true;
}
}

return false;
}

public function move(string $source, string $destination, ?ContextInterface $context = null): void
{
foreach ($this->adapters as $adapter) {
if ($adapter instanceof MoveAwareInterface) {
$adapter->move($source, $destination, $context);
continue;
}

$adapter->add($destination, $adapter->get($source, $context), $context);
$adapter->remove($source, $context);
}
}
}
27 changes: 27 additions & 0 deletions Adapter/CopyAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Component\Filesystem\Adapter;

use SonsOfPHP\Component\Filesystem\ContextInterface;
use SonsOfPHP\Component\Filesystem\Exception\FilesystemException;
use SonsOfPHP\Component\Filesystem\Exception\FileNotFoundException;

/**
* If an adapter supports the ability to copy a file from one place to another
* they should implement this interface.
*
* @author Joshua Estes <[email protected]>
*/
interface CopyAwareInterface
{
/**
* Copies file from $source to $destination
*
* @throws FileNotFoundException When $source does not exist
* @throws FilesystemException Generic Failure Exception, might be throw
* if destintation exists
*/
public function copy(string $source, string $destination, ?ContextInterface $context = null): void;
}
23 changes: 23 additions & 0 deletions Adapter/DirectoryAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Component\Filesystem\Adapter;

use SonsOfPHP\Component\Filesystem\ContextInterface;
use SonsOfPHP\Component\Filesystem\Exception\FilesystemException;
use SonsOfPHP\Component\Filesystem\Exception\FileNotFoundException;

/**
* If an adapter is able to manage directories, it should implement this
* interface.
*
* @author Joshua Estes <[email protected]>
*/
interface DirectoryAwareInterface
{
/**
* @throws FilesystemException Generic Failure Exception
*/
public function isDirectory(string $path, ?ContextInterface $context = null): bool;
}
98 changes: 98 additions & 0 deletions Adapter/InMemoryAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Component\Filesystem\Adapter;

use SonsOfPHP\Component\Filesystem\ContextInterface;
use SonsOfPHP\Component\Filesystem\Exception\FileNotFoundException;
use SonsOfPHP\Component\Filesystem\Exception\UnableToReadFileException;

/**
* Just keeps files in memory, does not write anything to disk
*
* Usage:
* $adapter = new InMemoryAdapter();
*
* @author Joshua Estes <[email protected]>
*/
final class InMemoryAdapter implements AdapterInterface, CopyAwareInterface, MoveAwareInterface, DirectoryAwareInterface
{
private array $files = [];

public function add(string $path, mixed $contents, ?ContextInterface $context = null): void
{
$path = $this->normalizePath($path);

if (is_resource($contents)) {
$contents = stream_get_contents($contents, null, 0);
}

$this->files[$path] = $contents;
}

public function get(string $path, ?ContextInterface $context = null): string
{
$path = $this->normalizePath($path);

if (!array_key_exists($path, $this->files)) {
throw new UnableToReadFileException(sprintf('No file was found at "%s"', $path));
}

return $this->files[$path];
}

public function remove(string $path, ?ContextInterface $context = null): void
{
$path = $this->normalizePath($path);

unset($this->files[$path]);
}

public function copy(string $source, string $destination, ?ContextInterface $context = null): void
{
$source = $this->normalizePath($source);
$destination = $this->normalizePath($destination);

$this->files[$destination] = $this->files[$source];
}

public function move(string $source, string $destination, ?ContextInterface $context = null): void
{
$this->copy($source, $destination);
$this->remove($source);
}

public function has(string $path, ?ContextInterface $context = null): bool
{
return $this->isFile($path) || $this->isDirectory($path);
}

public function isFile(string $path, ?ContextInterface $context = null): bool
{
$path = $this->normalizePath($path);

return array_key_exists($path, $this->files);
}

public function isDirectory(string $path, ?ContextInterface $context = null): bool
{
$path = $this->normalizePath($path);

foreach ($this->files as $key => $contents) {
$parts = explode('/', $key);
array_pop($parts);

if (implode('/', $parts) === $path) {
return true;
}
}

return false;
}

private function normalizePath(string $path): string
{
return ltrim($path, '/');
}
}
27 changes: 27 additions & 0 deletions Adapter/MoveAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace SonsOfPHP\Component\Filesystem\Adapter;

use SonsOfPHP\Component\Filesystem\ContextInterface;
use SonsOfPHP\Component\Filesystem\Exception\FilesystemException;
use SonsOfPHP\Component\Filesystem\Exception\FileNotFoundException;

/**
* If an adapter supports the ability to move a file from one place to another
* they should implement this interface.
*
* @author Joshua Estes <[email protected]>
*/
interface MoveAwareInterface
{
/**
* Moves file from $source to $destination
*
* @throws FileNotFoundException When $source does not exist
* @throws FilesystemException Generic Failure Exception, might be throw
* if destintation exists
*/
public function move(string $source, string $destination, ?ContextInterface $context = null): void;
}
Loading

0 comments on commit 9e1d0cf

Please sign in to comment.