From de59f0d414a0c8f2f3c75f9f556c6b235ce08dfb Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Sun, 25 Aug 2024 12:53:53 -0400 Subject: [PATCH 1/4] Additional Bridges for Filesystem component --- bard.json | 16 +- .../Bridge/Aws/Filesystem/.gitattributes | 4 + .../Bridge/Aws/Filesystem/.gitignore | 3 + .../Aws/Filesystem/Adapter/S3Adapter.php | 182 ++++++++++++++++++ src/SonsOfPHP/Bridge/Aws/Filesystem/LICENSE | 19 ++ src/SonsOfPHP/Bridge/Aws/Filesystem/README.md | 17 ++ .../Bridge/Aws/Filesystem/composer.json | 54 ++++++ .../LiipImagine/Filesystem/.gitattributes | 4 + .../Bridge/LiipImagine/Filesystem/.gitignore | 3 + .../Loader/SonsOfPHPFilesystemLoader.php | 36 ++++ .../Resolver/SonsOfPHPFilesystemResolver.php | 111 +++++++++++ .../Bridge/LiipImagine/Filesystem/LICENSE | 19 ++ .../Bridge/LiipImagine/Filesystem/README.md | 17 ++ .../LiipImagine/Filesystem/composer.json | 57 ++++++ 14 files changed, 538 insertions(+), 4 deletions(-) create mode 100644 src/SonsOfPHP/Bridge/Aws/Filesystem/.gitattributes create mode 100644 src/SonsOfPHP/Bridge/Aws/Filesystem/.gitignore create mode 100644 src/SonsOfPHP/Bridge/Aws/Filesystem/Adapter/S3Adapter.php create mode 100644 src/SonsOfPHP/Bridge/Aws/Filesystem/LICENSE create mode 100644 src/SonsOfPHP/Bridge/Aws/Filesystem/README.md create mode 100644 src/SonsOfPHP/Bridge/Aws/Filesystem/composer.json create mode 100644 src/SonsOfPHP/Bridge/LiipImagine/Filesystem/.gitattributes create mode 100644 src/SonsOfPHP/Bridge/LiipImagine/Filesystem/.gitignore create mode 100644 src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php create mode 100644 src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php create mode 100644 src/SonsOfPHP/Bridge/LiipImagine/Filesystem/LICENSE create mode 100644 src/SonsOfPHP/Bridge/LiipImagine/Filesystem/README.md create mode 100644 src/SonsOfPHP/Bridge/LiipImagine/Filesystem/composer.json diff --git a/bard.json b/bard.json index d70c02bc..1407d31e 100644 --- a/bard.json +++ b/bard.json @@ -65,10 +65,22 @@ "path": "src/SonsOfPHP/Component/FeatureToggle", "repository": "git@github.com:SonsOfPHP/feature-toggle.git" }, + { + "path": "src/SonsOfPHP/Contract/Filesystem", + "repository": "git@github.com:SonsOfPHP/filesystem-contract.git" + }, { "path": "src/SonsOfPHP/Component/Filesystem", "repository": "git@github.com:SonsOfPHP/filesystem.git" }, + { + "path": "src/SonsOfPHP/Bridge/Aws/Filesystem", + "repository": "git@github.com:SonsOfPHP/filesystem-aws.git" + }, + { + "path": "src/SonsOfPHP/Bridge/LiipImagine/Filesystem", + "repository": "git@github.com:SonsOfPHP/filesystem-liip-imagine.git" + }, { "path": "src/SonsOfPHP/Component/HttpFactory", "repository": "git@github.com:SonsOfPHP/http-factory.git" @@ -141,10 +153,6 @@ "path": "src/SonsOfPHP/Contract/FeatureToggle", "repository": "git@github.com:SonsOfPHP/feature-toggle-contract.git" }, - { - "path": "src/SonsOfPHP/Contract/Filesystem", - "repository": "git@github.com:SonsOfPHP/filesystem-contract.git" - }, { "path": "src/SonsOfPHP/Contract/Logger", "repository": "git@github.com:SonsOfPHP/logger-contract.git" diff --git a/src/SonsOfPHP/Bridge/Aws/Filesystem/.gitattributes b/src/SonsOfPHP/Bridge/Aws/Filesystem/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Aws/Filesystem/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Bridge/Aws/Filesystem/.gitignore b/src/SonsOfPHP/Bridge/Aws/Filesystem/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Aws/Filesystem/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Bridge/Aws/Filesystem/Adapter/S3Adapter.php b/src/SonsOfPHP/Bridge/Aws/Filesystem/Adapter/S3Adapter.php new file mode 100644 index 00000000..df34160d --- /dev/null +++ b/src/SonsOfPHP/Bridge/Aws/Filesystem/Adapter/S3Adapter.php @@ -0,0 +1,182 @@ + + */ +final readonly class S3Adapter implements AdapterInterface, CopyAwareInterface, DirectoryAwareInterface, MoveAwareInterface +{ + public function __construct( + private S3ClientInterface $client, + private string $bucket, + private string $acl = 'private', + ) {} + + public function add(string $path, mixed $contents, ?ContextInterface $context = null): void + { + $options = [ + 'Bucket' => $this->bucket, + 'Prefix' => ltrim($path, '/'), + 'params' => [], + ]; + if (isset($context['mimeType'])) { + $options['params']['ContentType'] = $context['mimeType']; + } + try { + $this->client->upload( + $this->bucket, + ltrim($path, '/'), + $contents, + $context['acl'] ?? $this->acl, + $options, + ); + } catch (Throwable $exception) { + throw new UnableToWriteFileException($exception->getMessage()); + } + } + + public function get(string $path, ?ContextInterface $context = null): mixed + { + $options = [ + 'Bucket' => $this->bucket, + 'Key' => ltrim($path, '/'), + 'MaxKeys' => 1, + 'Delimiter' => '/', + ]; + $command = $this->client->getCommand('GetObject', $options); + + try { + return (string) $this->client->execute($command)->get('Body')->getContents(); + } catch (Throwable) { + throw new FilesystemException('Could not read file'); + } + } + + public function remove(string $path, ?ContextInterface $context = null): void + { + $options = [ + 'Bucket' => $this->bucket, + 'Key' => ltrim($path, '/'), + ]; + $command = $this->client->getCommand('DeleteObject', $options); + + try { + $this->client->execute($command); + } catch (Throwable $exception) { + throw new UnableToDeleteFileException($exception->getMessage()); + } + } + + public function has(string $path, ?ContextInterface $context = null): bool + { + if ($this->isFile($path, $context)) { + return true; + } + return $this->isDirectory($path, $context); + } + + public function isFile(string $path, ?ContextInterface $context = null): bool + { + try { + return $this->client->doesObjectExistV2($this->bucket, ltrim($path, '/'), false); + } catch (Throwable) { + throw new FilesystemException('Unable to check if file exists'); + } + } + + public function copy(string $source, string $destination, ?ContextInterface $context = null): void + { + try { + $this->client->copy( + $this->bucket, + ltrim($source, '/'), + $this->bucket, + ltrim($destination, '/'), + $context['acl'] ?? $this->acl, + ); + } catch (Throwable) { + throw new UnableToCopyFileException(); + } + } + + public function isDirectory(string $path, ?ContextInterface $context = null): bool + { + $options = [ + 'Bucket' => $this->bucket, + 'Prefix' => ltrim($path, '/'), + 'MaxKeys' => 1, + 'Delimiter' => '/', + ]; + $command = $this->client->getCommand('ListObjectsV2', $options); + + try { + $result = $this->client->execute($command); + if ($result->hasKey('Contents')) { + return true; + } + return $result->hasKey('CommonPrefixes'); + } catch (Throwable) { + throw new FilesystemException('Could not figure out if directory exists'); + } + } + + public function makeDirectory(string $path, ?ContextInterface $context = null): void + { + $this->write($path, '', $context); + } + + public function removeDirectory(string $path, ?ContextInterface $context = null): void + { + try { + $this->client->deleteMatchingObjects($this->bucket, $path); + } catch (Throwable) { + throw new UnableToDeleteDirectoryException(); + } + } + + public function move(string $source, string $destination, ?ContextInterface $context = null): void + { + try { + $this->copy($source, $destination, $context); + $this->delete($source); + } catch (Throwable) { + throw new UnableToMoveFile(); + } + } + + public function mimeType(string $path, ?ContextInterface $context = null): string + { + $options = [ + 'Bucket' => $this->bucket, + 'Key' => ltrim($path, '/'), + ]; + $command = $this->client->getCommand('HeadObject', $options); + + try { + $result = $this->client->execute($command); + } catch (Throwable) { + throw new FilesystemException(); + } + + return $result['ContentType']; + } +} diff --git a/src/SonsOfPHP/Bridge/Aws/Filesystem/LICENSE b/src/SonsOfPHP/Bridge/Aws/Filesystem/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Aws/Filesystem/LICENSE @@ -0,0 +1,19 @@ +Copyright 2022 to Present Joshua Estes + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/SonsOfPHP/Bridge/Aws/Filesystem/README.md b/src/SonsOfPHP/Bridge/Aws/Filesystem/README.md new file mode 100644 index 00000000..7ee53445 --- /dev/null +++ b/src/SonsOfPHP/Bridge/Aws/Filesystem/README.md @@ -0,0 +1,17 @@ +Sons of PHP - Money - Twig Bridge +================================= + +## Learn More + +* [Documentation][docs] +* [Contributing][contributing] +* [Report Issues][issues] and [Submit Pull Requests][pull-requests] in the + [Mother Repository][mother-repo] +* Get Help & Support using [Discussions][discussions] + +[discussions]: https://github.com/orgs/SonsOfPHP/discussions +[mother-repo]: https://github.com/SonsOfPHP/sonsofphp +[contributing]: https://docs.sonsofphp.com/contributing/ +[docs]: https://docs.sonsofphp.com/components/money/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AMoney +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AMoney diff --git a/src/SonsOfPHP/Bridge/Aws/Filesystem/composer.json b/src/SonsOfPHP/Bridge/Aws/Filesystem/composer.json new file mode 100644 index 00000000..a3ab354e --- /dev/null +++ b/src/SonsOfPHP/Bridge/Aws/Filesystem/composer.json @@ -0,0 +1,54 @@ +{ + "name": "sonsofphp/filesystem-aws", + "type": "library", + "description": "Additional adapters for Sons of PHP Filesystem Component", + "keywords": [ + "sonsofphp", + "aws", + "filesystem" + ], + "homepage": "https://github.com/SonsOfPHP/filesystem-aws", + "license": "MIT", + "authors": [ + { + "name": "Joshua Estes", + "email": "joshua@sonsofphp.com" + } + ], + "support": { + "issues": "https://github.com/SonsOfPHP/sonsofphp/issues", + "forum": "https://github.com/orgs/SonsOfPHP/discussions", + "docs": "https://docs.sonsofphp.com" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Bridge\\Aws\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.2", + "sonsofphp/filesystem": "^0.3.x-dev", + "aws/aws-sdk-php": "^3.0" + }, + "extra": { + "sort-packages": true, + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/JoshuaEstes" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" + } + ] +} diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/.gitattributes b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/.gitattributes new file mode 100644 index 00000000..84c7add0 --- /dev/null +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/.gitignore b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/.gitignore new file mode 100644 index 00000000..5414c2c6 --- /dev/null +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php new file mode 100644 index 00000000..26450263 --- /dev/null +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php @@ -0,0 +1,36 @@ +filesystem->mimeType($path); + $extension = $this->extensionGuesser->getExtensions($mimeType)[0] ?? null; + + return new Binary( + $this->filesystem->read($path), + $mimeType, + $extension, + ); + } catch(FilesystemExceptionInterface $exception) { + throw new NotLoadableException(sprintf('Source image "%s" not found.', $path), 0, $exception); + } + } +} diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php new file mode 100644 index 00000000..e687cfa1 --- /dev/null +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php @@ -0,0 +1,111 @@ +webRoot = rtrim($webRoot, '/'); + $this->cachePrefix = ltrim(str_replace('//', '/', $cachePrefix), '/'); + } + + /** + * Checks whether the given path is stored within this Resolver. + * + * @param string $path + * @param string $filter + * + * @return bool + */ + public function isStored($path, string $filter): bool + { + return $this->filesystem->exists($this->getFilePath($path, $filter)); + } + + /** + * Resolves filtered path for rendering in the browser. + * + * @param string $path The path where the original file is expected to be + * @param string $filter The name of the imagine filter in effect + * + * @throws NotResolvableException + * + * @return string The absolute URL of the cached image + */ + public function resolve($path, string $filter): string + { + return sprintf( + '%s/%s', + rtrim($this->webRoot, '/'), + ltrim((string) $this->getFileUrl($path, $filter), '/'), + ); + } + + /** + * Stores the content of the given binary. + * + * @param BinaryInterface $binary The image binary to store + * @param string $path The path where the original file is expected to be + * @param string $filter The name of the imagine filter in effect + */ + public function store(BinaryInterface $binary, $path, string $filter): void + { + $this->filesystem->write( + $this->getFilePath($path, $filter), + $binary->getContent(), + ['mimeType' => $binary->getMimeType()], + ); + } + + /** + * @param string[] $paths The paths where the original files are expected to be + * @param string[] $filters The imagine filters in effect + */ + public function remove(array $paths, array $filters): void + { + if ($paths === [] && $filters === []) { + return; + } + + if ($paths === []) { + foreach ($filters as $filter) { + $filterCacheDir = $this->cachePrefix . '/' . $filter; + $this->filesystem->delete($filterCacheDir); + } + + return; + } + + foreach ($paths as $path) { + foreach ($filters as $filter) { + if ($this->filesystem->exists($this->getFilePath($path, $filter))) { + $this->filesystem->delete($this->getFilePath($path, $filter)); + } + } + } + } + + protected function getFilePath($path, string $filter): string + { + return $this->getFileUrl($path, $filter); + } + + protected function getFileUrl($path, string $filter): string + { + // crude way of sanitizing URL scheme ("protocol") part + $path = str_replace('://', '---', $path); + + return $this->cachePrefix . '/' . $filter . '/' . ltrim($path, '/'); + } +} diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/LICENSE b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/LICENSE @@ -0,0 +1,19 @@ +Copyright 2022 to Present Joshua Estes + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/README.md b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/README.md new file mode 100644 index 00000000..7ee53445 --- /dev/null +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/README.md @@ -0,0 +1,17 @@ +Sons of PHP - Money - Twig Bridge +================================= + +## Learn More + +* [Documentation][docs] +* [Contributing][contributing] +* [Report Issues][issues] and [Submit Pull Requests][pull-requests] in the + [Mother Repository][mother-repo] +* Get Help & Support using [Discussions][discussions] + +[discussions]: https://github.com/orgs/SonsOfPHP/discussions +[mother-repo]: https://github.com/SonsOfPHP/sonsofphp +[contributing]: https://docs.sonsofphp.com/contributing/ +[docs]: https://docs.sonsofphp.com/components/money/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AMoney +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AMoney diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/composer.json b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/composer.json new file mode 100644 index 00000000..e273871b --- /dev/null +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/composer.json @@ -0,0 +1,57 @@ +{ + "name": "sonsofphp/filesystem-liip-imagine", + "type": "library", + "description": "Provides support for Sons of PHP Filesystem component", + "keywords": [ + "sonsofphp", + "liip", + "imagine", + "liip-imagine", + "liip-imagine-bundle", + "filesystem" + ], + "homepage": "https://github.com/SonsOfPHP/filesystem-liip-imagine", + "license": "MIT", + "authors": [ + { + "name": "Joshua Estes", + "email": "joshua@sonsofphp.com" + } + ], + "support": { + "issues": "https://github.com/SonsOfPHP/sonsofphp/issues", + "forum": "https://github.com/orgs/SonsOfPHP/discussions", + "docs": "https://docs.sonsofphp.com" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Bridge\\LiipImagine\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.2", + "sonsofphp/filesystem": "^0.3.x-dev", + "liip/imagine-bundle": "^2.0" + }, + "extra": { + "sort-packages": true, + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/JoshuaEstes" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" + } + ] +} From 8b5d1fd3995ce5788ae1ebd1f09fd9358c816a25 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Sun, 25 Aug 2024 12:55:14 -0400 Subject: [PATCH 2/4] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 524b0b02..94720b56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ To get the diff between two versions, go to https://github.com/SonsOfPHP/sonsofp ## [Unreleased] +* [PR #220](https://github.com/SonsOfPHP/sonsofphp/pull/220) [Filesystem] Adds bridges for AWS and LiipImagineBundle * [PR #214](https://github.com/SonsOfPHP/sonsofphp/pull/214) [Logger] Bug fixes and updates * [PR #51](https://github.com/SonsOfPHP/sonsofphp/pull/51) Added new Filesystem component * [PR #59](https://github.com/SonsOfPHP/sonsofphp/pull/59) Added new HttpMessage component From d4777c9c20bf90a5662ab68af5915e60366e3c2e Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Sun, 25 Aug 2024 13:06:25 -0400 Subject: [PATCH 3/4] updates and docs --- docs/components/filesystem/adapters.md | 16 ++++++++++ docs/components/filesystem/index.md | 31 +++++++++++++++++++ .../Loader/SonsOfPHPFilesystemLoader.php | 3 ++ .../Resolver/SonsOfPHPFilesystemResolver.php | 3 ++ 4 files changed, 53 insertions(+) diff --git a/docs/components/filesystem/adapters.md b/docs/components/filesystem/adapters.md index be48d226..78c50d93 100644 --- a/docs/components/filesystem/adapters.md +++ b/docs/components/filesystem/adapters.md @@ -100,3 +100,19 @@ use SonsOfPHP\Component\Filesystem\Adapter\NativeAdapter; $adapter = new WormAdapter(new NativeAdater('/tmp')); ``` + +## Additional Adapters + +### AWS S3 + +```shell +composer require sonsofphp/filesystem-aws +``` + +```php +copy('source.txt', 'destination.txt'); $filesystem->move('source.txt', 'destination.txt'); ``` +## Support for LiipImagineBundle + +```shell +composer require sonsofphp/filesystem-liip-imagine +``` + +```yaml +# config/services.yaml +services: + SonsOfPHP\Contract\Filesystem\Adapter\AdapterInterface: + class: SonsOfPHP\Component\Filesystem\Adapter\NativeAdapter + arguments: ['%kernel.project_dir%/var/data/%kernel.id%'] + SonsOfPHP\Contract\Filesystem\FilesystemInterface: + class: SonsOfPHP\Component\Filesystem\Filesystem + arguments: ['@SonsOfPHP\Contract\Filesystem\Adapter\AdapterInterface'] + imagine.cache.resolver.sonsofphp: + class: SonsOfPHP\Bridge\LiipImagine\Filesystem\Imagine\Cache\Resolver\SonsOfPHPFilesystemResolver + arguments: + - '@SonsOfPHP\Contract\Filesystem\FilesystemInterface' + - 'https://images.example.com' + tags: + - { name: "liip_imagine.cache.resolver", resolver: sonsofphp } +``` + +```yaml +# config/packages/liip_imagine.yaml +liip_imagine: + data_loader: SonsOfPHP\Bridge\LiipImagine\Filesystem\Binary\Loader\SonsOfPHPFilesystemLoader + cache: sonsofphp +``` + ## Need Help? Check out [Sons of PHP's Organization Discussions][discussions]. diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php index 26450263..e30dfc7f 100644 --- a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php @@ -11,6 +11,9 @@ use SonsOfPHP\Contract\Filesystem\FilesystemInterface; use Symfony\Component\Mime\MimeTypesInterface; +/** + * @author Joshua Estes + */ class SonsOfPHPFilesystemLoader implements LoaderInterface { public function __construct( diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php index e687cfa1..263c2ad5 100644 --- a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php @@ -9,6 +9,9 @@ use Liip\ImagineBundle\Imagine\Cache\Resolver\ResolverInterface; use SonsOfPHP\Contract\Filesystem\FilesystemInterface; +/** + * @author Joshua Estes + */ class SonsOfPHPFilesystemResolver implements ResolverInterface { public function __construct( From 9bab60a31dc6ca7f6a799f478d05b038e81c65dd Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Mon, 26 Aug 2024 12:39:02 -0400 Subject: [PATCH 4/4] testing --- composer.json | 16 +- phpunit.xml.dist | 2 + .../Aws/Filesystem/Adapter/S3Adapter.php | 7 +- .../Tests/Adapter/S3AdapterTest.php | 140 ++++++++++++++++++ .../Bridge/Aws/Filesystem/composer.json | 2 +- .../Loader/SonsOfPHPFilesystemLoader.php | 2 +- .../Resolver/SonsOfPHPFilesystemResolver.php | 6 +- .../Loader/SonsOfPHPFilesystemLoaderTest.php | 57 +++++++ .../SonsOfPHPFilesystemResolverTest.php | 85 +++++++++++ .../LiipImagine/Filesystem/composer.json | 2 +- .../Filesystem/Adapter/InMemoryAdapter.php | 27 +++- .../Tests/Adapter/InMemoryAdapterTest.php | 94 ++++++------ src/SonsOfPHP/Component/Logger/composer.json | 2 +- .../Contract/Filesystem/composer.json | 4 +- 14 files changed, 382 insertions(+), 64 deletions(-) create mode 100644 src/SonsOfPHP/Bridge/Aws/Filesystem/Tests/Adapter/S3AdapterTest.php create mode 100644 src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests/Binary/Loader/SonsOfPHPFilesystemLoaderTest.php create mode 100644 src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolverTest.php diff --git a/composer.json b/composer.json index ceab81f6..6550f063 100644 --- a/composer.json +++ b/composer.json @@ -76,7 +76,7 @@ "psr/http-factory": "^1.0", "psr/cache": "^2.0 || ^3.0", "psr/simple-cache": "^3.0", - "psr/log": "^1.0 || ^2.0 || ^3.0", + "psr/log": "^2.0 || ^3.0", "psr/link": "^1.0 || ^2.0", "twig/twig": "^3.0", "ext-intl": "*", @@ -84,7 +84,9 @@ "doctrine/orm": "^2 || ^3", "psr/container": "^1.0 || ^2.0", "psr/http-server-handler": "^1.0", - "psr/http-server-middleware": "^1.0" + "psr/http-server-middleware": "^1.0", + "aws/aws-sdk-php": "^3.0", + "liip/imagine-bundle": "^2.0" }, "replace": { "sonsofphp/bard": "self.version", @@ -127,7 +129,9 @@ "sonsofphp/http-handler": "self.version", "sonsofphp/http-handler-contract": "self.version", "sonsofphp/mailer-contract": "self.version", - "sonsofphp/mailer": "self.version" + "sonsofphp/mailer": "self.version", + "sonsofphp/filesystem-aws": "self.version", + "sonsofphp/filesystem-liip-imagine": "self.version" }, "autoload": { "psr-4": { @@ -147,7 +151,10 @@ "SonsOfPHP\\Component\\EventSourcing\\": "src/SonsOfPHP/Component/EventSourcing", "SonsOfPHP\\Bridge\\Doctrine\\EventSourcing\\": "src/SonsOfPHP/Bridge/Doctrine/EventSourcing", "SonsOfPHP\\Component\\FeatureToggle\\": "src/SonsOfPHP/Component/FeatureToggle", + "SonsOfPHP\\Contract\\Filesystem\\": "src/SonsOfPHP/Contract/Filesystem", "SonsOfPHP\\Component\\Filesystem\\": "src/SonsOfPHP/Component/Filesystem", + "SonsOfPHP\\Bridge\\Aws\\Filesystem\\": "src/SonsOfPHP/Bridge/Aws/Filesystem", + "SonsOfPHP\\Bridge\\LiipImagine\\Filesystem\\": "src/SonsOfPHP/Bridge/LiipImagine/Filesystem", "SonsOfPHP\\Component\\HttpFactory\\": "src/SonsOfPHP/Component/HttpFactory", "SonsOfPHP\\Component\\HttpHandler\\": "src/SonsOfPHP/Component/HttpHandler", "SonsOfPHP\\Contract\\HttpHandler\\": "src/SonsOfPHP/Contract/HttpHandler", @@ -166,7 +173,6 @@ "SonsOfPHP\\Contract\\Cqrs\\": "src/SonsOfPHP/Contract/Cqrs", "SonsOfPHP\\Contract\\EventSourcing\\": "src/SonsOfPHP/Contract/EventSourcing", "SonsOfPHP\\Contract\\FeatureToggle\\": "src/SonsOfPHP/Contract/FeatureToggle", - "SonsOfPHP\\Contract\\Filesystem\\": "src/SonsOfPHP/Contract/Filesystem", "SonsOfPHP\\Contract\\Logger\\": "src/SonsOfPHP/Contract/Logger", "SonsOfPHP\\Contract\\Money\\": "src/SonsOfPHP/Contract/Money", "SonsOfPHP\\Contract\\Pager\\": "src/SonsOfPHP/Contract/Pager", @@ -189,6 +195,8 @@ "src/SonsOfPHP/Bridge/Doctrine/EventSourcing/Tests", "src/SonsOfPHP/Component/FeatureToggle/Tests", "src/SonsOfPHP/Component/Filesystem/Tests", + "src/SonsOfPHP/Bridge/Aws/Filesystem/Tests", + "src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests", "src/SonsOfPHP/Component/HttpFactory/Tests", "src/SonsOfPHP/Component/HttpHandler/Tests", "src/SonsOfPHP/Component/HttpMessage/Tests", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1d748e5f..c1abb041 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -54,6 +54,8 @@ src/SonsOfPHP/Component/FeatureToggle/Tests + src/SonsOfPHP/Bridge/Aws/Filesystem/Tests + src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests diff --git a/src/SonsOfPHP/Bridge/Aws/Filesystem/Adapter/S3Adapter.php b/src/SonsOfPHP/Bridge/Aws/Filesystem/Adapter/S3Adapter.php index df34160d..01d68ddb 100644 --- a/src/SonsOfPHP/Bridge/Aws/Filesystem/Adapter/S3Adapter.php +++ b/src/SonsOfPHP/Bridge/Aws/Filesystem/Adapter/S3Adapter.php @@ -91,6 +91,7 @@ public function has(string $path, ?ContextInterface $context = null): bool if ($this->isFile($path, $context)) { return true; } + return $this->isDirectory($path, $context); } @@ -141,7 +142,7 @@ public function isDirectory(string $path, ?ContextInterface $context = null): bo public function makeDirectory(string $path, ?ContextInterface $context = null): void { - $this->write($path, '', $context); + $this->add($path, '', $context); } public function removeDirectory(string $path, ?ContextInterface $context = null): void @@ -157,7 +158,7 @@ public function move(string $source, string $destination, ?ContextInterface $con { try { $this->copy($source, $destination, $context); - $this->delete($source); + $this->remove($source); } catch (Throwable) { throw new UnableToMoveFile(); } @@ -177,6 +178,6 @@ public function mimeType(string $path, ?ContextInterface $context = null): strin throw new FilesystemException(); } - return $result['ContentType']; + return $result->get('ContentType'); } } diff --git a/src/SonsOfPHP/Bridge/Aws/Filesystem/Tests/Adapter/S3AdapterTest.php b/src/SonsOfPHP/Bridge/Aws/Filesystem/Tests/Adapter/S3AdapterTest.php new file mode 100644 index 00000000..28ae50ad --- /dev/null +++ b/src/SonsOfPHP/Bridge/Aws/Filesystem/Tests/Adapter/S3AdapterTest.php @@ -0,0 +1,140 @@ +stream = $this->createMock(StreamInterface::class); + + $this->result = $this->createMock(ResultInterface::class); + + $this->command = $this->createMock(CommandInterface::class); + + $this->client = $this->createMock(S3ClientInterface::class); + $this->client->method('getCommand')->willReturn($this->command); + $this->client->method('execute')->willReturn($this->result); + + $this->adapter = new S3Adapter($this->client, 'test-bucket'); + } + + public function testItHasTheRightInterfaces(): void + { + $this->assertInstanceOf(AdapterInterface::class, $this->adapter); + $this->assertInstanceOf(CopyAwareInterface::class, $this->adapter); + $this->assertInstanceOf(DirectoryAwareInterface::class, $this->adapter); + $this->assertInstanceOf(MoveAwareInterface::class, $this->adapter); + } + + public function testItWillUploadFile(): void + { + $this->client->expects($this->once())->method('upload'); + + $this->adapter->add('/path/to/file.ext', ''); + } + + public function testItCanGetFileContents(): void + { + $this->stream->method('getContents')->willReturn('file contents'); + $this->result->method('get')->with($this->equalTo('Body'))->willReturn($this->stream); + + $this->assertSame('file contents', $this->adapter->get('/path/to/file.ext')); + } + + public function testItCanRemoveFile(): void + { + $this->client->expects($this->once())->method('getCommand')->with($this->equalTo('DeleteObject'))->willReturn($this->command); + + $this->adapter->remove('/path/to/file.ext'); + } + + public function testItHasFile(): void + { + $this->client->expects($this->once())->method('doesObjectExistV2')->willReturn(true); + + $this->assertTrue($this->adapter->has('/path/to/file.ext')); + } + + public function testItHasDirectory(): void + { + $this->client->expects($this->once())->method('doesObjectExistV2')->willReturn(false); + $this->result->method('hasKey')->willReturn(true); + + $this->assertTrue($this->adapter->has('/path/to')); + } + + public function testIsFile(): void + { + $this->client->expects($this->once())->method('doesObjectExistV2')->willReturn(true); + + $this->assertTrue($this->adapter->isFile('/path/to/file.ext')); + } + + public function testItCanCopyFile(): void + { + $this->client->expects($this->once())->method('copy'); + + $this->adapter->copy('/path/to/source.ext', '/path/to/destination.ext'); + } + + public function testIsDirectory(): void + { + $this->result->method('hasKey')->willReturn(true); + + $this->assertTrue($this->adapter->isDirectory('/path/to')); + } + + public function testItCanMakeDirectory(): void + { + $this->client->expects($this->once())->method('upload'); + + $this->adapter->makeDirectory('/path/to'); + } + + public function testItCanRemoveDirectory(): void + { + $this->client->expects($this->once())->method('deleteMatchingObjects'); + + $this->adapter->removeDirectory('/path/to'); + } + + public function testItCanMoveFile(): void + { + $this->client->expects($this->once())->method('copy'); + $this->client->expects($this->once())->method('getCommand')->with($this->equalTo('DeleteObject'))->willReturn($this->command); + + $this->adapter->move('/path/to/source.ext', '/path/to/destination.ext'); + } + + public function testItCanReturnContentType(): void + { + $this->result + ->method('get') + ->with($this->equalTo('ContentType')) + ->willReturn('text/plain'); + + $this->assertSame('text/plain', $this->adapter->mimeType('/path/to/source.ext')); + } +} diff --git a/src/SonsOfPHP/Bridge/Aws/Filesystem/composer.json b/src/SonsOfPHP/Bridge/Aws/Filesystem/composer.json index a3ab354e..45a86403 100644 --- a/src/SonsOfPHP/Bridge/Aws/Filesystem/composer.json +++ b/src/SonsOfPHP/Bridge/Aws/Filesystem/composer.json @@ -51,4 +51,4 @@ "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" } ] -} +} \ No newline at end of file diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php index e30dfc7f..7f5d84dd 100644 --- a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Binary/Loader/SonsOfPHPFilesystemLoader.php @@ -21,7 +21,7 @@ public function __construct( private readonly MimeTypesInterface $extensionGuesser, ) {} - public function find(string $path) + public function find($path) { try { $mimeType = $this->filesystem->mimeType($path); diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php index 263c2ad5..2069111e 100644 --- a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolver.php @@ -31,7 +31,7 @@ public function __construct( * * @return bool */ - public function isStored($path, string $filter): bool + public function isStored($path, $filter): bool { return $this->filesystem->exists($this->getFilePath($path, $filter)); } @@ -46,7 +46,7 @@ public function isStored($path, string $filter): bool * * @return string The absolute URL of the cached image */ - public function resolve($path, string $filter): string + public function resolve($path, $filter): string { return sprintf( '%s/%s', @@ -62,7 +62,7 @@ public function resolve($path, string $filter): string * @param string $path The path where the original file is expected to be * @param string $filter The name of the imagine filter in effect */ - public function store(BinaryInterface $binary, $path, string $filter): void + public function store(BinaryInterface $binary, $path, $filter): void { $this->filesystem->write( $this->getFilePath($path, $filter), diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests/Binary/Loader/SonsOfPHPFilesystemLoaderTest.php b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests/Binary/Loader/SonsOfPHPFilesystemLoaderTest.php new file mode 100644 index 00000000..a24b63b6 --- /dev/null +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests/Binary/Loader/SonsOfPHPFilesystemLoaderTest.php @@ -0,0 +1,57 @@ +filesystem = $this->createMock(FilesystemInterface::class); + + $this->mimeTypes = $this->createMock(MimeTypesInterface::class); + + $this->loader = new SonsOfPHPFilesystemLoader($this->filesystem, $this->mimeTypes); + } + + public function testItHasTheRightInterfaces(): void + { + $this->assertInstanceOf(LoaderInterface::class, $this->loader); + } + + public function testItWillFindAndReturnABinaryObject(): void + { + $this->filesystem->method('mimeType')->willReturn('image/png'); + $this->filesystem->method('read')->willReturn('pretend this is binary data'); + + $this->mimeTypes->method('getExtensions')->willReturn(['png']); + + $output = $this->loader->find('/path/to/file.png'); + $this->assertInstanceOf(BinaryInterface::class, $output); + } + + public function testItWillThrowCorrectExceptionWhenFileNotFound(): void + { + $this->filesystem->method('read')->will($this->throwException(new FileNotFoundException())); + ; + + $this->expectException(NotLoadableException::class); + $this->loader->find('/path/to/file.png'); + } +} diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolverTest.php b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolverTest.php new file mode 100644 index 00000000..45324530 --- /dev/null +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/Tests/Imagine/Cache/Resolver/SonsOfPHPFilesystemResolverTest.php @@ -0,0 +1,85 @@ +filesystem = new Filesystem(new InMemoryAdapter()); + + $this->resolver = new SonsOfPHPFilesystemResolver($this->filesystem, $this->webRoot); + } + + public function testItHasTheRightInterface(): void + { + $this->assertInstanceOf(ResolverInterface::class, $this->resolver); + } + + public function testItWillReturnTrueWhenFileIsStored(): void + { + $this->filesystem->write('media/cache/cache/path/to/file.ext', 'contents'); + $this->assertTrue($this->resolver->isStored('/path/to/file.ext', 'cache')); + } + + public function testItWillReturnFlaseWhenFileIsNotStored(): void + { + $this->assertFalse($this->resolver->isStored('/path/to/file.ext', 'cache')); + } + + public function testItCanResolve(): void + { + $this->assertSame('https://images.internal/media/cache/cache/path/to/file.ext', $this->resolver->resolve('/path/to/file.ext', 'cache')); + } + + public function testItCanStore(): void + { + $binary = new Binary('contents', 'image/png', 'png'); + $this->resolver->store($binary, '/path/to/file.ext', 'cache'); + $this->assertTrue($this->filesystem->exists('/media/cache/cache/path/to/file.ext')); + } + + #[DoesNotPerformAssertions] + public function testItCanRemoveWhenPathsAndFiltersAreEmtpty(): void + { + $this->resolver->remove([], []); + } + + public function testItCanRemoveByFilters(): void + { + $this->filesystem->write('media/cache/cache/path/to/file.ext', 'contents'); + $this->resolver->remove([], ['cache']); + $this->assertFalse($this->filesystem->exists('/media/cache/cache/path/to/file.ext')); + } + + public function testItCanRemoveByPathsAndFilters(): void + { + $this->filesystem->write('/media/cache/cache/path/to/file.ext', 'contents'); + $this->filesystem->write('/media/cache/cache/path/to/another.ext', 'contents'); + $this->resolver->remove(['/path/to/file.ext'], ['cache']); + $this->assertFalse($this->filesystem->exists('/media/cache/cache/path/to/file.ext')); + $this->assertTrue($this->filesystem->exists('/media/cache/cache/path/to/another.ext')); + } +} diff --git a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/composer.json b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/composer.json index e273871b..ca625487 100644 --- a/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/composer.json +++ b/src/SonsOfPHP/Bridge/LiipImagine/Filesystem/composer.json @@ -54,4 +54,4 @@ "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" } ] -} +} \ No newline at end of file diff --git a/src/SonsOfPHP/Component/Filesystem/Adapter/InMemoryAdapter.php b/src/SonsOfPHP/Component/Filesystem/Adapter/InMemoryAdapter.php index c5d059c8..c8bb84f8 100644 --- a/src/SonsOfPHP/Component/Filesystem/Adapter/InMemoryAdapter.php +++ b/src/SonsOfPHP/Component/Filesystem/Adapter/InMemoryAdapter.php @@ -50,7 +50,11 @@ public function remove(string $path, ?ContextInterface $context = null): void { $path = $this->normalizePath($path); - unset($this->files[$path]); + foreach ($this->files as $key => $value) { + if ($path === $key || str_starts_with($key, $path)) { + unset($this->files[$key]); + } + } } public function copy(string $source, string $destination, ?ContextInterface $context = null): void @@ -87,7 +91,7 @@ public function isDirectory(string $path, ?ContextInterface $context = null): bo $parts = explode('/', $key); array_pop($parts); - if (implode('/', $parts) === $path) { + if (str_starts_with($key, $path)) { return true; } } @@ -100,9 +104,24 @@ public function mimeType(string $path, ?ContextInterface $context = null): strin throw new FilesystemException('Not Supported'); } - public function makeDirectory(string $path, ?ContextInterface $context = null): void {} + public function makeDirectory(string $path, ?ContextInterface $context = null): void + { + $path = $this->normalizePath($path); + + $this->files[$path] = null; + } + + public function removeDirectory(string $path, ?ContextInterface $context = null): void + { + $path = $this->normalizePath($path); - public function removeDirectory(string $path, ?ContextInterface $context = null): void {} + foreach ($this->files as $key => $value) { + if (str_starts_with($key, $path)) { + unset($this->files[$key]); + return; + } + } + } private function normalizePath(string $path): string { diff --git a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/InMemoryAdapterTest.php b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/InMemoryAdapterTest.php index 46945ddd..0d08f445 100644 --- a/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/InMemoryAdapterTest.php +++ b/src/SonsOfPHP/Component/Filesystem/Tests/Adapter/InMemoryAdapterTest.php @@ -10,102 +10,108 @@ use SonsOfPHP\Component\Filesystem\Exception\UnableToReadFileException; use SonsOfPHP\Contract\Filesystem\Adapter\AdapterInterface; -/** - * @uses \SonsOfPHP\Component\Filesystem\Adapter\InMemoryAdapter - * @coversNothing - */ #[CoversClass(InMemoryAdapter::class)] final class InMemoryAdapterTest extends TestCase { - /** - * @coversNothing - */ - public function testItHasTheCorrectInterface(): void + private AdapterInterface $adapter; + + protected function setUp(): void { - $adapter = new InMemoryAdapter(); + $this->adapter = new InMemoryAdapter(); + } - $this->assertInstanceOf(AdapterInterface::class, $adapter); + public function testItHasTheCorrectInterface(): void + { + $this->assertInstanceOf(AdapterInterface::class, $this->adapter); } public function testItCanWriteAFileAndReadItLater(): void { - $adapter = new InMemoryAdapter(); - $contents = 'Pretend this is the contents of a file'; - $adapter->add('/path/to/test.txt', $contents); - $this->assertSame($contents, $adapter->get('/path/to/test.txt')); + $this->adapter->add('/path/to/test.txt', $contents); + $this->assertSame($contents, $this->adapter->get('/path/to/test.txt')); } public function testItWillThrowExceptionWhenFileNotFound(): void { - $adapter = new InMemoryAdapter(); $this->expectException(UnableToReadFileException::class); - $adapter->get('test.txt'); + $this->adapter->get('test.txt'); } public function testItCanDeleteFile(): void { - $adapter = new InMemoryAdapter(); - $adapter->add('/path/to/test.txt', 'testing'); - $this->assertSame('testing', $adapter->get('/path/to/test.txt')); + $this->adapter->add('/path/to/test.txt', 'testing'); + $this->assertSame('testing', $this->adapter->get('/path/to/test.txt')); - $adapter->remove('/path/to/test.txt'); + $this->adapter->remove('/path/to/test.txt'); $this->expectException(UnableToReadFileException::class); - $adapter->get('/path/to/test.txt'); + $this->adapter->get('/path/to/test.txt'); } public function testItCanCopyFile(): void { - $adapter = new InMemoryAdapter(); - $adapter->add('test.txt', 'testing'); - $adapter->copy('test.txt', 'test2.txt'); - $this->assertSame('testing', $adapter->get('test.txt')); - $this->assertSame('testing', $adapter->get('test2.txt')); + $this->adapter->add('test.txt', 'testing'); + $this->adapter->copy('test.txt', 'test2.txt'); + $this->assertSame('testing', $this->adapter->get('test.txt')); + $this->assertSame('testing', $this->adapter->get('test2.txt')); } public function testItCanMoveFile(): void { - $adapter = new InMemoryAdapter(); - $adapter->add('test.txt', 'testing'); - $adapter->move('test.txt', 'test2.txt'); - $this->assertSame('testing', $adapter->get('test2.txt')); + $this->adapter->add('test.txt', 'testing'); + $this->adapter->move('test.txt', 'test2.txt'); + $this->assertSame('testing', $this->adapter->get('test2.txt')); $this->expectException(UnableToReadFileException::class); - $adapter->get('test.txt'); + $this->adapter->get('test.txt'); } public function testItCanSupportStream(): void { - $adapter = new InMemoryAdapter(); $stream = fopen('php://memory', 'w+'); fwrite($stream, 'Just a test'); - $adapter->add('test.txt', $stream); + $this->adapter->add('test.txt', $stream); fclose($stream); - $this->assertSame('Just a test', $adapter->get('test.txt')); + $this->assertSame('Just a test', $this->adapter->get('test.txt')); } public function testItCanCheckIfFilesExist(): void { - $adapter = new InMemoryAdapter(); - $this->assertFalse($adapter->has('test.txt')); - $adapter->add('test.txt', 'testing'); - $this->assertTrue($adapter->has('test.txt')); + $this->assertFalse($this->adapter->has('test.txt')); + $this->adapter->add('test.txt', 'testing'); + $this->assertTrue($this->adapter->has('test.txt')); } public function testItCanCheckIfIsFile(): void { - $adapter = new InMemoryAdapter(); - $adapter->add('/path/to/file.txt', 'testing'); + $this->adapter->add('/path/to/file.txt', 'testing'); - $this->assertTrue($adapter->isFile('/path/to/file.txt')); + $this->assertTrue($this->adapter->isFile('/path/to/file.txt')); } public function testItCanCheckIfIsDirectory(): void { - $adapter = new InMemoryAdapter(); - $adapter->add('/path/to/file.txt', 'testing'); + $this->adapter->add('/path/to/file.txt', 'testing'); - $this->assertTrue($adapter->isDirectory('/path/to')); + $this->assertTrue($this->adapter->isDirectory('/path/to')); + } + + public function testItWillDeleteAllFilesInDirectory(): void + { + $this->adapter->add('/path/to/one.txt', 'testing'); + $this->adapter->add('/path/to/two.txt', 'testing'); + $this->adapter->remove('/path/to'); + $this->assertFalse($this->adapter->isFile('/path/to/one.ext')); + $this->assertFalse($this->adapter->isFile('/path/to/two.ext')); + } + + public function testItWillRemoveDirectroy(): void + { + $this->adapter->add('/path/to/one.txt', 'testing'); + $this->adapter->add('/path/to/two.txt', 'testing'); + $this->adapter->removeDirectory('/path/to'); + $this->assertFalse($this->adapter->isFile('/path/to/one.ext')); + $this->assertFalse($this->adapter->isFile('/path/to/two.ext')); } } diff --git a/src/SonsOfPHP/Component/Logger/composer.json b/src/SonsOfPHP/Component/Logger/composer.json index bfe60270..22a0ac23 100644 --- a/src/SonsOfPHP/Component/Logger/composer.json +++ b/src/SonsOfPHP/Component/Logger/composer.json @@ -55,4 +55,4 @@ "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" } ] -} +} \ No newline at end of file diff --git a/src/SonsOfPHP/Contract/Filesystem/composer.json b/src/SonsOfPHP/Contract/Filesystem/composer.json index 295e05d1..4f5aac56 100644 --- a/src/SonsOfPHP/Contract/Filesystem/composer.json +++ b/src/SonsOfPHP/Contract/Filesystem/composer.json @@ -21,7 +21,7 @@ "support": { "issues": "https://github.com/SonsOfPHP/sonsofphp/issues", "forum": "https://github.com/orgs/SonsOfPHP/discussions", - "docs": "https://docs.sonsofphp.com/contracts/filesystem" + "docs": "https://docs.sonsofphp.com" }, "autoload": { "psr-4": { @@ -49,4 +49,4 @@ "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" } ] -} +} \ No newline at end of file