From a26fa29cc9a992cc385be52b5f200297d137e1d6 Mon Sep 17 00:00:00 2001 From: Sergey Romanenko Date: Sun, 2 Jan 2022 13:16:59 +0300 Subject: [PATCH 01/46] Update license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 7c1027d3e..1f0165218 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2020 Frank de Jonge +Copyright (c) 2013-2022 Frank de Jonge Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 7c67bd718328e0e653a9f30b59bdbb33a04090b9 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 4 Jan 2022 18:03:22 +0100 Subject: [PATCH 02/46] Publish subsplit for sftp-v3 --- config.subsplit-publish.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.subsplit-publish.json b/config.subsplit-publish.json index 7f2047483..b0de91ee2 100644 --- a/config.subsplit-publish.json +++ b/config.subsplit-publish.json @@ -10,6 +10,11 @@ "directory": "src/PhpseclibV2", "target": "git@github.com:thephpleague/flysystem-sftp.git" }, + { + "name": "sftp-v3", + "directory": "src/PhpseclibV3", + "target": "git@github.com:thephpleague/flysystem-sftp-v3.git" + }, { "name": "memory", "directory": "src/InMemory", From 4a6f9f7d7b97c938f2fd25aeb9ff9a6a34c79f2f Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 4 Jan 2022 18:09:29 +0100 Subject: [PATCH 03/46] Prepare changelog for SFTP V3 release. --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42abe853d..da16588a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Version 2.x Changelog +## 2.4.0 - 2022-01-04 + +### Added + +- [SFTP V3] New adapter officially published + ## 2.3.2 - 2021-11-28 ### Fixed From 6e418367f6702dc9be394a6674e3a08f8ea86702 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 11 Jan 2022 21:15:55 +0100 Subject: [PATCH 04/46] Use different method for checking permissiosn --- src/Local/LocalFilesystemAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Local/LocalFilesystemAdapter.php b/src/Local/LocalFilesystemAdapter.php index 83310f037..50bd6430c 100644 --- a/src/Local/LocalFilesystemAdapter.php +++ b/src/Local/LocalFilesystemAdapter.php @@ -210,7 +210,7 @@ public function listContents(string $path, bool $deep): iterable $path = $this->prefixer->stripPrefix($fileInfo->getPathname()); $lastModified = $fileInfo->getMTime(); $isDirectory = $fileInfo->isDir(); - $permissions = octdec(substr(sprintf('%o', $fileInfo->getPerms()), -4)); + $permissions = $fileInfo->getPerms() & 0777; $visibility = $isDirectory ? $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions); yield $isDirectory ? new DirectoryAttributes($path, $visibility, $lastModified) : new FileAttributes( From 21ee228cf12f40643e7893157be6d0224660870c Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 11 Jan 2022 21:18:38 +0100 Subject: [PATCH 05/46] revert --- src/Local/LocalFilesystemAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Local/LocalFilesystemAdapter.php b/src/Local/LocalFilesystemAdapter.php index 50bd6430c..83310f037 100644 --- a/src/Local/LocalFilesystemAdapter.php +++ b/src/Local/LocalFilesystemAdapter.php @@ -210,7 +210,7 @@ public function listContents(string $path, bool $deep): iterable $path = $this->prefixer->stripPrefix($fileInfo->getPathname()); $lastModified = $fileInfo->getMTime(); $isDirectory = $fileInfo->isDir(); - $permissions = $fileInfo->getPerms() & 0777; + $permissions = octdec(substr(sprintf('%o', $fileInfo->getPerms()), -4)); $visibility = $isDirectory ? $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions); yield $isDirectory ? new DirectoryAttributes($path, $visibility, $lastModified) : new FileAttributes( From edc152aaef99210a2f2ee8649ff1b73d762affc8 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 11 Jan 2022 21:50:41 +0100 Subject: [PATCH 06/46] Sort listing before checking visibility. --- src/Local/LocalFilesystemAdapterTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Local/LocalFilesystemAdapterTest.php b/src/Local/LocalFilesystemAdapterTest.php index fc924d794..c9aa0050b 100644 --- a/src/Local/LocalFilesystemAdapterTest.php +++ b/src/Local/LocalFilesystemAdapterTest.php @@ -4,6 +4,8 @@ namespace League\Flysystem\Local; +use function strnatcasecmp; +use function usort; use const LOCK_EX; use League\Flysystem\AdapterTestUtilities\FilesystemAdapterTestCase; use League\Flysystem\Config; @@ -302,13 +304,17 @@ public function retrieving_visibility_while_listing_directory_contents(): void /** @var Traversable $contentListing */ $contentListing = $adapter->listContents('/', true); + $listing = iterator_to_array($contentListing); + usort($listing, function(StorageAttributes $a, StorageAttributes $b) { + return strnatcasecmp($a->path(), $b->path()); + }); /** * @var StorageAttributes $publicDirectoryAttributes * @var StorageAttributes $privateFileAttributes * @var StorageAttributes $privateDirectoryAttributes * @var StorageAttributes $publicFileAttributes */ - [$publicDirectoryAttributes, $privateFileAttributes, $privateDirectoryAttributes, $publicFileAttributes] = iterator_to_array($contentListing); + [$privateDirectoryAttributes, $publicFileAttributes, $publicDirectoryAttributes, $privateFileAttributes] = $listing; $this->assertEquals('public', $publicDirectoryAttributes->visibility()); $this->assertEquals('private', $privateFileAttributes->visibility()); From e924ab9e4abff6d93599e2c6db9c3948496a6976 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 21:39:38 +0100 Subject: [PATCH 07/46] only create tags of 2.* --- .github/workflows/publish-subsplits.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-subsplits.yml b/.github/workflows/publish-subsplits.yml index 2c6a4c201..4b0ebdf6d 100644 --- a/.github/workflows/publish-subsplits.yml +++ b/.github/workflows/publish-subsplits.yml @@ -6,10 +6,10 @@ on: - 2.x create: tags: - - '*' + - '2.*' delete: tags: - - '*' + - '2.*' jobs: publish_subsplits: From 30288b740997686d0d1b60f748bc168623f97b10 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 11 Jan 2022 23:04:22 +0100 Subject: [PATCH 08/46] Increase PHP and other version deps --- .github/workflows/quality-assurance.yml | 13 ++++--------- composer.json | 9 +++------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.github/workflows/quality-assurance.yml b/.github/workflows/quality-assurance.yml index 0b39c9da3..c7af9b571 100644 --- a/.github/workflows/quality-assurance.yml +++ b/.github/workflows/quality-assurance.yml @@ -28,23 +28,20 @@ jobs: continue-on-error: ${{ matrix.experimental }} strategy: matrix: - php: [ '7.2', '7.3', '7.4' ] - composer-flags: [ '' ] + php: [ '8.0' ] + composer-flags: [ '--no-coverage' ] experimental: [false] - upgrade-aws-sdk: [ 'no' ] phpunit-flags: [ '--coverage-text' ] include: - php: '8.0' composer-flags: '' experimental: false phpunit-flags: '--no-coverage' - upgrade-aws-sdk: 'yes' - php: '8.1' composer-flags: '--ignore-platform-reqs' - experimental: true + experimental: false phpunit-flags: '--no-coverage' - upgrade-aws-sdk: 'yes' - - php: '7.2' + - php: '8.0' composer-flags: '--prefer-lowest' experimental: false phpunit-flags: '--no-coverage' @@ -62,8 +59,6 @@ jobs: coverage: pcov tools: composer:v2 - run: composer update --no-progress ${{ matrix.composer-flags }} - - run: composer require --dev --ignore-platform-reqs aws/aws-sdk-php:^3.147.3 - if: ${{ matrix.upgrade-aws-sdk == 'yes' }} - run: php test_files/wait_for_sftp.php - run: php test_files/wait_for_ftp.php 2121 - run: php test_files/wait_for_ftp.php 2122 diff --git a/composer.json b/composer.json index be532f613..487dde514 100644 --- a/composer.json +++ b/composer.json @@ -17,16 +17,16 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "ext-json": "*", "league/mime-type-detection": "^1.0.0" }, "require-dev": { "ext-fileinfo": "*", - "phpunit/phpunit": "^8.5 || ^9.4", + "phpunit/phpunit": "^9.4", "phpstan/phpstan": "^0.12.26", "phpseclib/phpseclib": "^2.0", - "aws/aws-sdk-php": "^3.132.4", + "aws/aws-sdk-php": "^3.198.1", "composer/semver": "^3.0", "friendsofphp/php-cs-fixer": "^3.2", "google/cloud-storage": "^1.23", @@ -34,9 +34,6 @@ "async-aws/simple-s3": "^1.0", "sabre/dav": "^4.1" }, - "conflict": { - "guzzlehttp/ringphp": "<1.1.1" - }, "license": "MIT", "authors": [ { From 46bbbf4c68dd0bfefcd25c5350e60ee8e1c4e413 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 08:59:24 +0100 Subject: [PATCH 09/46] Remove --no-coverage from composer, it does not exist. --- .github/workflows/quality-assurance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality-assurance.yml b/.github/workflows/quality-assurance.yml index c7af9b571..9405a211f 100644 --- a/.github/workflows/quality-assurance.yml +++ b/.github/workflows/quality-assurance.yml @@ -29,7 +29,7 @@ jobs: strategy: matrix: php: [ '8.0' ] - composer-flags: [ '--no-coverage' ] + composer-flags: [ '' ] experimental: [false] phpunit-flags: [ '--coverage-text' ] include: From d26e5555818d697d30e9e25e7f9634f201e0b37e Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 09:01:28 +0100 Subject: [PATCH 10/46] Do not fail fast --- .github/workflows/quality-assurance.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/quality-assurance.yml b/.github/workflows/quality-assurance.yml index 9405a211f..36cb4acc7 100644 --- a/.github/workflows/quality-assurance.yml +++ b/.github/workflows/quality-assurance.yml @@ -27,6 +27,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental }} strategy: + fail-fast: false matrix: php: [ '8.0' ] composer-flags: [ '' ] From c8ff975081a0f3004be3c7818791ad723c01a34c Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 20:18:20 +0100 Subject: [PATCH 11/46] Run on 3.x branch PR's --- .github/workflows/quality-assurance.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/quality-assurance.yml b/.github/workflows/quality-assurance.yml index 36cb4acc7..16f8bfe6e 100644 --- a/.github/workflows/quality-assurance.yml +++ b/.github/workflows/quality-assurance.yml @@ -13,6 +13,7 @@ on: - .github/workflows/quality-assurance.yml branches: - 2.x + - 3.x env: FLYSYSTEM_AWS_S3_KEY: '${{ secrets.FLYSYSTEM_AWS_S3_KEY }}' From 0c88773f40eaa5437e9b07c377f6430a480cfb3a Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 20:32:31 +0100 Subject: [PATCH 12/46] Increase phpunit add conflict with buggy symfony/http-client --- composer.json | 5 ++++- src/AsyncAwsS3/composer.json | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 487dde514..b3d1106bc 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ }, "require-dev": { "ext-fileinfo": "*", - "phpunit/phpunit": "^9.4", + "phpunit/phpunit": "^9.5.11", "phpstan/phpstan": "^0.12.26", "phpseclib/phpseclib": "^2.0", "aws/aws-sdk-php": "^3.198.1", @@ -34,6 +34,9 @@ "async-aws/simple-s3": "^1.0", "sabre/dav": "^4.1" }, + "conflict": { + "symfony/http-client": "<5.2" + }, "license": "MIT", "authors": [ { diff --git a/src/AsyncAwsS3/composer.json b/src/AsyncAwsS3/composer.json index 178f59907..fcc5289f5 100644 --- a/src/AsyncAwsS3/composer.json +++ b/src/AsyncAwsS3/composer.json @@ -17,6 +17,9 @@ "require-dev": { "async-aws/simple-s3": "^1.0" }, + "conflict": { + "symfony/http-client": "<5.2" + }, "license": "MIT", "authors": [ { From 33030d813002add321712f8b0246d2440912b4e9 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 20:43:19 +0100 Subject: [PATCH 13/46] Added conflict with buddy guzzlehttp/ringphp version --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b3d1106bc..17ef57d1c 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,8 @@ "sabre/dav": "^4.1" }, "conflict": { - "symfony/http-client": "<5.2" + "symfony/http-client": "<5.2", + "guzzlehttp/ringphp": "<1.1.1" }, "license": "MIT", "authors": [ From b9cd6bdda50d2bf71a9660f17f43b8947842502b Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 20:47:37 +0100 Subject: [PATCH 14/46] Conflict with buggy guzzle version --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 17ef57d1c..e090459ef 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ }, "conflict": { "symfony/http-client": "<5.2", - "guzzlehttp/ringphp": "<1.1.1" + "guzzlehttp/ringphp": "<1.1.1", + "guzzlehttp/guzzle": "<7.0" }, "license": "MIT", "authors": [ From 71854e2c1316e94f0359339fadeb1374b042db2c Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 20:54:54 +0100 Subject: [PATCH 15/46] Increase PHP versions for subsplits --- src/AdapterTestUtilities/composer.json | 2 +- src/AsyncAwsS3/composer.json | 2 +- src/AwsS3V3/composer.json | 5 +++-- src/Ftp/composer.json | 2 +- src/GoogleCloudStorage/composer.json | 2 +- src/InMemory/composer.json | 2 +- src/PhpseclibV2/composer.json | 2 +- src/PhpseclibV3/composer.json | 2 +- src/ZipArchive/composer.json | 2 +- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/AdapterTestUtilities/composer.json b/src/AdapterTestUtilities/composer.json index b58d94dcd..71de42e1a 100644 --- a/src/AdapterTestUtilities/composer.json +++ b/src/AdapterTestUtilities/composer.json @@ -13,7 +13,7 @@ ] }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "league/flysystem": "^2.0.0" }, "license": "MIT", diff --git a/src/AsyncAwsS3/composer.json b/src/AsyncAwsS3/composer.json index fcc5289f5..cc865f75e 100644 --- a/src/AsyncAwsS3/composer.json +++ b/src/AsyncAwsS3/composer.json @@ -9,7 +9,7 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "league/flysystem": "^2.0.0", "league/mime-type-detection": "^1.0.0", "async-aws/s3": "^1.5" diff --git a/src/AwsS3V3/composer.json b/src/AwsS3V3/composer.json index 01170ee0c..827b8abe5 100644 --- a/src/AwsS3V3/composer.json +++ b/src/AwsS3V3/composer.json @@ -9,13 +9,14 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "league/flysystem": "^2.0.0", "league/mime-type-detection": "^1.0.0", "aws/aws-sdk-php": "^3.132.4" }, "conflict": { - "guzzlehttp/ringphp": "<1.1.1" + "guzzlehttp/ringphp": "<1.1.1", + "guzzlehttp/guzzle": "<7.0" }, "license": "MIT", "authors": [ diff --git a/src/Ftp/composer.json b/src/Ftp/composer.json index d9d1b56ad..96f056e67 100644 --- a/src/Ftp/composer.json +++ b/src/Ftp/composer.json @@ -10,7 +10,7 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "ext-ftp": "*", "league/flysystem": "^2.0.0", "league/mime-type-detection": "^1.0.0" diff --git a/src/GoogleCloudStorage/composer.json b/src/GoogleCloudStorage/composer.json index 686e8068a..951d91b19 100644 --- a/src/GoogleCloudStorage/composer.json +++ b/src/GoogleCloudStorage/composer.json @@ -10,7 +10,7 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "google/cloud-storage": "^1.23", "league/flysystem": "^2.0.0", "league/mime-type-detection": "^1.0.0" diff --git a/src/InMemory/composer.json b/src/InMemory/composer.json index 9bf7f732e..6b41dfd0f 100644 --- a/src/InMemory/composer.json +++ b/src/InMemory/composer.json @@ -11,7 +11,7 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "ext-fileinfo": "*", "league/flysystem": "^2.0.0" }, diff --git a/src/PhpseclibV2/composer.json b/src/PhpseclibV2/composer.json index 71bd19d49..53a9878f6 100644 --- a/src/PhpseclibV2/composer.json +++ b/src/PhpseclibV2/composer.json @@ -8,7 +8,7 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "league/flysystem": "^2.0.0", "league/mime-type-detection": "^1.0.0", "phpseclib/phpseclib": "^2.0" diff --git a/src/PhpseclibV3/composer.json b/src/PhpseclibV3/composer.json index ef8fd0b11..f06b3fc35 100644 --- a/src/PhpseclibV3/composer.json +++ b/src/PhpseclibV3/composer.json @@ -8,7 +8,7 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "league/flysystem": "^2.0.0", "league/mime-type-detection": "^1.0.0", "phpseclib/phpseclib": "^3.0" diff --git a/src/ZipArchive/composer.json b/src/ZipArchive/composer.json index 486963771..afd1f0c4a 100644 --- a/src/ZipArchive/composer.json +++ b/src/ZipArchive/composer.json @@ -10,7 +10,7 @@ } }, "require": { - "php": "^7.2 || ^8.0", + "php": "^8.0.2", "ext-zip": "*", "league/flysystem": "^2.0.0", "league/mime-type-detection": "^1.0.0" From 770180643120db338ade24765fc7880be2ae4a81 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 20:59:31 +0100 Subject: [PATCH 16/46] simplify matrix --- .github/workflows/quality-assurance.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/quality-assurance.yml b/.github/workflows/quality-assurance.yml index 16f8bfe6e..dd7c37a89 100644 --- a/.github/workflows/quality-assurance.yml +++ b/.github/workflows/quality-assurance.yml @@ -30,19 +30,11 @@ jobs: strategy: fail-fast: false matrix: - php: [ '8.0' ] + php: [ '8.0', '8.1' ] composer-flags: [ '' ] experimental: [false] phpunit-flags: [ '--coverage-text' ] include: - - php: '8.0' - composer-flags: '' - experimental: false - phpunit-flags: '--no-coverage' - - php: '8.1' - composer-flags: '--ignore-platform-reqs' - experimental: false - phpunit-flags: '--no-coverage' - php: '8.0' composer-flags: '--prefer-lowest' experimental: false From 89561b760d29012a0aedb71de352a5971b8260d9 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 11 Jan 2022 20:37:20 +0100 Subject: [PATCH 17/46] Added directoryExists method. --- .../FilesystemAdapterTestCase.php | 35 +++++++++++++++++++ src/AsyncAwsS3/AsyncAwsS3Adapter.php | 10 ++++++ src/AwsS3V3/AwsS3V3Adapter.php | 24 +++++++++---- src/Filesystem.php | 5 +++ src/FilesystemAdapter.php | 5 +++ src/FilesystemOperationFailed.php | 1 + src/FilesystemReader.php | 6 ++++ src/FilesystemTest.php | 14 ++++++++ src/Ftp/FtpAdapter.php | 8 +++++ .../GoogleCloudStorageAdapter.php | 8 +++++ src/InMemory/InMemoryFilesystemAdapter.php | 19 ++++++++++ src/Local/LocalFilesystemAdapter.php | 7 ++++ src/PhpseclibV2/SftpAdapter.php | 7 ++++ src/PhpseclibV3/SftpAdapter.php | 7 ++++ src/UnableToCheckDirectoryExistence.php | 21 +++++++++++ src/ZipArchive/ZipArchiveAdapter.php | 8 +++++ 16 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 src/UnableToCheckDirectoryExistence.php diff --git a/src/AdapterTestUtilities/FilesystemAdapterTestCase.php b/src/AdapterTestUtilities/FilesystemAdapterTestCase.php index e5e9453b2..5ff70717f 100644 --- a/src/AdapterTestUtilities/FilesystemAdapterTestCase.php +++ b/src/AdapterTestUtilities/FilesystemAdapterTestCase.php @@ -298,6 +298,41 @@ public function listing_contents_shallow(): void }); } + /** + * @test + */ + public function checking_if_a_non_existing_directory_exists(): void + { + $this->runScenario(function () { + $adapter = $this->adapter(); + self::assertFalse($adapter->directoryExists('this-does-not-exist.php')); + }); + } + + /** + * @test + */ + public function checking_if_a_directory_exists_after_writing_a_file(): void + { + $this->runScenario(function () { + $adapter = $this->adapter(); + $this->givenWeHaveAnExistingFile('existing-directory/file.txt'); + self::assertTrue($adapter->directoryExists('existing-directory')); + }); + } + + /** + * @test + */ + public function checking_if_a_directory_exists_after_creating_it(): void + { + $this->runScenario(function () { + $adapter = $this->adapter(); + $adapter->createDirectory('explicitly-created-directory', new Config()); + self::assertTrue($adapter->directoryExists('explicitly-created-directory')); + }); + } + /** * @test */ diff --git a/src/AsyncAwsS3/AsyncAwsS3Adapter.php b/src/AsyncAwsS3/AsyncAwsS3Adapter.php index c9a293b92..a29ef4ad2 100644 --- a/src/AsyncAwsS3/AsyncAwsS3Adapter.php +++ b/src/AsyncAwsS3/AsyncAwsS3Adapter.php @@ -31,6 +31,8 @@ use League\MimeTypeDetection\MimeTypeDetector; use Throwable; +use function trim; + class AsyncAwsS3Adapter implements FilesystemAdapter { /** @@ -254,6 +256,14 @@ public function fileSize(string $path): FileAttributes return $attributes; } + public function directoryExists(string $path): bool + { + $prefix = $this->prefixer->prefixDirectoryPath($path); + $options = ['Bucket' => $this->bucket, 'Prefix' => $prefix, 'Delimiter' => '/']; + + return $this->client->listObjectsV2($options)->getKeyCount() > 0; + } + public function listContents(string $path, bool $deep): iterable { $prefix = trim($this->prefixer->prefixPath($path), '/'); diff --git a/src/AwsS3V3/AwsS3V3Adapter.php b/src/AwsS3V3/AwsS3V3Adapter.php index 15b7eb26d..157eb09a5 100644 --- a/src/AwsS3V3/AwsS3V3Adapter.php +++ b/src/AwsS3V3/AwsS3V3Adapter.php @@ -14,6 +14,7 @@ use League\Flysystem\FilesystemOperationFailed; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; +use League\Flysystem\UnableToCheckDirectoryExistence; use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToDeleteFile; @@ -28,6 +29,8 @@ use Psr\Http\Message\StreamInterface; use Throwable; +use function trim; + class AwsS3V3Adapter implements FilesystemAdapter { /** @@ -57,7 +60,6 @@ class AwsS3V3Adapter implements FilesystemAdapter 'Tagging', 'WebsiteRedirectLocation', ]; - /** * @var string[] */ @@ -130,6 +132,19 @@ public function fileExists(string $path): bool } } + public function directoryExists(string $path): bool + { + try { + $prefix = $this->prefixer->prefixDirectoryPath($path); + $options = ['Bucket' => $this->bucket, 'Prefix' => $prefix, 'Delimiter' => '/']; + $command = $this->client->getCommand('ListObjects', $options); + + return $this->client->execute($command)->hasKey('Contents'); + } catch (Throwable $exception) { + throw UnableToCheckDirectoryExistence::forLocation($path, $exception); + } + } + public function write(string $path, string $contents, Config $config): void { $this->upload($path, $contents, $config); @@ -294,12 +309,7 @@ private function mapS3ObjectMetadata(array $metadata, string $path = null): Stor $lastModified = $dateTime instanceof DateTimeResult ? $dateTime->getTimeStamp() : null; return new FileAttributes( - $path, - $fileSize, - null, - $lastModified, - $mimetype, - $this->extractExtraMetadata($metadata) + $path, $fileSize, null, $lastModified, $mimetype, $this->extractExtraMetadata($metadata) ); } diff --git a/src/Filesystem.php b/src/Filesystem.php index f66574d68..58a14b18f 100644 --- a/src/Filesystem.php +++ b/src/Filesystem.php @@ -36,6 +36,11 @@ public function fileExists(string $location): bool return $this->adapter->fileExists($this->pathNormalizer->normalizePath($location)); } + public function directoryExists(string $location): bool + { + return $this->adapter->directoryExists($this->pathNormalizer->normalizePath($location)); + } + public function write(string $location, string $contents, array $config = []): void { $this->adapter->write( diff --git a/src/FilesystemAdapter.php b/src/FilesystemAdapter.php index 6dcb51e40..41ac68b2a 100644 --- a/src/FilesystemAdapter.php +++ b/src/FilesystemAdapter.php @@ -11,6 +11,11 @@ interface FilesystemAdapter */ public function fileExists(string $path): bool; + /** + * @throws FilesystemException + */ + public function directoryExists(string $path): bool; + /** * @throws UnableToWriteFile * @throws FilesystemException diff --git a/src/FilesystemOperationFailed.php b/src/FilesystemOperationFailed.php index 1c0b6df76..e0a7fcfa2 100644 --- a/src/FilesystemOperationFailed.php +++ b/src/FilesystemOperationFailed.php @@ -9,6 +9,7 @@ interface FilesystemOperationFailed extends FilesystemException public const OPERATION_WRITE = 'WRITE'; public const OPERATION_UPDATE = 'UPDATE'; public const OPERATION_FILE_EXISTS = 'FILE_EXISTS'; + public const OPERATION_DIRECTORY_EXISTS = 'DIRECTORY_EXISTS'; public const OPERATION_CREATE_DIRECTORY = 'CREATE_DIRECTORY'; public const OPERATION_DELETE = 'DELETE'; public const OPERATION_DELETE_DIRECTORY = 'DELETE_DIRECTORY'; diff --git a/src/FilesystemReader.php b/src/FilesystemReader.php index 63145d091..68b6f2199 100644 --- a/src/FilesystemReader.php +++ b/src/FilesystemReader.php @@ -19,6 +19,12 @@ interface FilesystemReader */ public function fileExists(string $location): bool; + /** + * @throws FilesystemException + * @throws UnableToCheckDirectoryExistence + */ + public function directoryExists(string $location): bool; + /** * @throws UnableToReadFile * @throws FilesystemException diff --git a/src/FilesystemTest.php b/src/FilesystemTest.php index 2b79d7669..35bdfb73a 100644 --- a/src/FilesystemTest.php +++ b/src/FilesystemTest.php @@ -103,6 +103,20 @@ public function checking_if_files_exist(): void $this->assertFalse($otherFileExists); } + /** + * @test + */ + public function checking_if_directories_exist(): void + { + $this->filesystem->createDirectory('existing-directory'); + + $existingDirectory = $this->filesystem->directoryExists('existing-directory'); + $notExistingDirectory = $this->filesystem->directoryExists('not-existing-directory'); + + $this->assertTrue($existingDirectory); + $this->assertFalse($notExistingDirectory); + } + /** * @test */ diff --git a/src/Ftp/FtpAdapter.php b/src/Ftp/FtpAdapter.php index a32922a5e..976fa3841 100644 --- a/src/Ftp/FtpAdapter.php +++ b/src/Ftp/FtpAdapter.php @@ -10,6 +10,7 @@ use League\Flysystem\DirectoryAttributes; use League\Flysystem\FileAttributes; use League\Flysystem\FilesystemAdapter; +use League\Flysystem\FilesystemException; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; use League\Flysystem\UnableToCopyFile; @@ -622,4 +623,11 @@ private function hasFtpConnection(): bool { return $this->connection instanceof \FTP\Connection || is_resource($this->connection); } + + public function directoryExists(string $path): bool + { + $connection = $this->connection(); + + return @ftp_chdir($connection, $path) === true; + } } diff --git a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php index 7bc6a3e60..272d843c2 100644 --- a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php +++ b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php @@ -11,6 +11,7 @@ use League\Flysystem\DirectoryAttributes; use League\Flysystem\FileAttributes; use League\Flysystem\FilesystemAdapter; +use League\Flysystem\FilesystemException; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; use League\Flysystem\UnableToCopyFile; @@ -61,6 +62,13 @@ public function fileExists(string $path): bool return $this->bucket->object($prefixedPath)->exists(); } + public function directoryExists(string $path): bool + { + $prefixedPath = $this->prefixer->prefixDirectoryPath($path); + + return $this->bucket->object($prefixedPath)->exists(); + } + public function write(string $path, string $contents, Config $config): void { $this->upload($path, $contents, $config); diff --git a/src/InMemory/InMemoryFilesystemAdapter.php b/src/InMemory/InMemoryFilesystemAdapter.php index 4c32bf167..d64c8d42c 100644 --- a/src/InMemory/InMemoryFilesystemAdapter.php +++ b/src/InMemory/InMemoryFilesystemAdapter.php @@ -8,6 +8,7 @@ use League\Flysystem\DirectoryAttributes; use League\Flysystem\FileAttributes; use League\Flysystem\FilesystemAdapter; +use League\Flysystem\FilesystemException; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToReadFile; @@ -17,6 +18,10 @@ use League\MimeTypeDetection\FinfoMimeTypeDetector; use League\MimeTypeDetection\MimeTypeDetector; +use function array_keys; +use function rtrim; +use function strpos; + class InMemoryFilesystemAdapter implements FilesystemAdapter { const DUMMY_FILE_FOR_FORCED_LISTING_IN_FLYSYSTEM_TEST = '______DUMMY_FILE_FOR_FORCED_LISTING_IN_FLYSYSTEM_TEST'; @@ -107,6 +112,20 @@ public function createDirectory(string $path, Config $config): void $this->write($filePath, '', $config); } + public function directoryExists(string $path): bool + { + $prefix = $this->preparePath($path); + $prefix = rtrim($prefix, '/') . '/'; + + foreach (array_keys($this->files) as $path) { + if (strpos($path, $prefix) === 0) { + return true; + } + } + + return false; + } + public function setVisibility(string $path, string $visibility): void { $path = $this->preparePath($path); diff --git a/src/Local/LocalFilesystemAdapter.php b/src/Local/LocalFilesystemAdapter.php index 83310f037..23d4f0071 100644 --- a/src/Local/LocalFilesystemAdapter.php +++ b/src/Local/LocalFilesystemAdapter.php @@ -304,6 +304,13 @@ public function fileExists(string $location): bool return is_file($location); } + public function directoryExists(string $location): bool + { + $location = $this->prefixer->prefixPath($location); + + return is_dir($location); + } + public function createDirectory(string $path, Config $config): void { $location = $this->prefixer->prefixPath($path); diff --git a/src/PhpseclibV2/SftpAdapter.php b/src/PhpseclibV2/SftpAdapter.php index 45b56fc66..c595f513f 100644 --- a/src/PhpseclibV2/SftpAdapter.php +++ b/src/PhpseclibV2/SftpAdapter.php @@ -66,6 +66,13 @@ public function fileExists(string $path): bool return $this->connectionProvider->provideConnection()->is_file($location); } + public function directoryExists(string $path): bool + { + $location = $this->prefixer->prefixDirectoryPath($path); + + return $this->connectionProvider->provideConnection()->is_dir($location); + } + /** * @param string $path * @param string|resource $contents diff --git a/src/PhpseclibV3/SftpAdapter.php b/src/PhpseclibV3/SftpAdapter.php index 64a13ab15..2cc94523f 100644 --- a/src/PhpseclibV3/SftpAdapter.php +++ b/src/PhpseclibV3/SftpAdapter.php @@ -66,6 +66,13 @@ public function fileExists(string $path): bool return $this->connectionProvider->provideConnection()->is_file($location); } + public function directoryExists(string $path): bool + { + $location = $this->prefixer->prefixDirectoryPath($path); + + return $this->connectionProvider->provideConnection()->is_dir($location); + } + /** * @param string $path * @param string|resource $contents diff --git a/src/UnableToCheckDirectoryExistence.php b/src/UnableToCheckDirectoryExistence.php new file mode 100644 index 000000000..023799f7c --- /dev/null +++ b/src/UnableToCheckDirectoryExistence.php @@ -0,0 +1,21 @@ +zipArchiveProvider->createZipArchive(); + $location = $this->pathPrefixer->prefixDirectoryPath($path); + + return $archive->statName($location) !== false; + } + public function setVisibility(string $path, string $visibility): void { $archive = $this->zipArchiveProvider->createZipArchive(); From c2cd0b768de94a386947230ddfc2214ccfd37ef9 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 11 Jan 2022 20:45:40 +0100 Subject: [PATCH 18/46] Added missing method for ExceptionThrowingFilesystemAdapter --- .../ExceptionThrowingFilesystemAdapter.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/AdapterTestUtilities/ExceptionThrowingFilesystemAdapter.php b/src/AdapterTestUtilities/ExceptionThrowingFilesystemAdapter.php index b85d04e1c..ec5b633dc 100644 --- a/src/AdapterTestUtilities/ExceptionThrowingFilesystemAdapter.php +++ b/src/AdapterTestUtilities/ExceptionThrowingFilesystemAdapter.php @@ -156,4 +156,11 @@ public function copy(string $source, string $destination, Config $config): void $this->adapter->copy($source, $destination, $config); } + + public function directoryExists(string $path): bool + { + $this->throwStagedException(__METHOD__, $path); + + return $this->adapter->directoryExists($path); + } } From ba99fbd7b34f853979b995e377d162d93e5ff104 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 11 Jan 2022 20:49:53 +0100 Subject: [PATCH 19/46] Added missing method for MountManager --- src/MountManager.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/MountManager.php b/src/MountManager.php index b5a777353..dbd6dc70c 100644 --- a/src/MountManager.php +++ b/src/MountManager.php @@ -4,6 +4,8 @@ namespace League\Flysystem; +use Throwable; + use function sprintf; class MountManager implements FilesystemOperator @@ -30,11 +32,23 @@ public function fileExists(string $location): bool try { return $filesystem->fileExists($path); - } catch (UnableToCheckFileExistence $exception) { + } catch (Throwable $exception) { throw UnableToCheckFileExistence::forLocation($location, $exception); } } + public function directoryExists(string $location): bool + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->directoryExists($path); + } catch (Throwable $exception) { + throw UnableToCheckDirectoryExistence::forLocation($location, $exception); + } + } + public function read(string $location): string { /** @var FilesystemOperator $filesystem */ From 4126e92f2404c05d161fcb8800b5bf6fa5c8bc56 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 11 Jan 2022 21:04:05 +0100 Subject: [PATCH 20/46] Clear stat cache before listing contents. --- src/Local/LocalFilesystemAdapter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Local/LocalFilesystemAdapter.php b/src/Local/LocalFilesystemAdapter.php index 23d4f0071..80316a74c 100644 --- a/src/Local/LocalFilesystemAdapter.php +++ b/src/Local/LocalFilesystemAdapter.php @@ -190,6 +190,7 @@ protected function deleteFileInfoObject(SplFileInfo $file): bool public function listContents(string $path, bool $deep): iterable { + clearstatcache(); $location = $this->prefixer->prefixPath($path); if ( ! is_dir($location)) { From 48ebe8c373619acb7dfefb54ddd151a5e33d4fd2 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Tue, 11 Jan 2022 22:47:50 +0100 Subject: [PATCH 21/46] Don't clear stat cache before listing contents --- src/Local/LocalFilesystemAdapter.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Local/LocalFilesystemAdapter.php b/src/Local/LocalFilesystemAdapter.php index 80316a74c..23d4f0071 100644 --- a/src/Local/LocalFilesystemAdapter.php +++ b/src/Local/LocalFilesystemAdapter.php @@ -190,7 +190,6 @@ protected function deleteFileInfoObject(SplFileInfo $file): bool public function listContents(string $path, bool $deep): iterable { - clearstatcache(); $location = $this->prefixer->prefixPath($path); if ( ! is_dir($location)) { From a384eaf311a0c82c90703f323a34a5b7d5840239 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 21:33:00 +0100 Subject: [PATCH 22/46] Allow splits to install flysystem 2.x and 3.x --- bin/check-versions.php | 2 +- src/AdapterTestUtilities/composer.json | 2 +- src/AsyncAwsS3/composer.json | 2 +- src/AwsS3V3/composer.json | 2 +- src/Ftp/composer.json | 2 +- src/GoogleCloudStorage/composer.json | 2 +- src/InMemory/composer.json | 2 +- src/PhpseclibV2/composer.json | 2 +- src/PhpseclibV3/composer.json | 2 +- src/ZipArchive/composer.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bin/check-versions.php b/bin/check-versions.php index 892ee0617..f98bb05ee 100644 --- a/bin/check-versions.php +++ b/bin/check-versions.php @@ -69,7 +69,7 @@ function constraint_has_conflict(string $mainConstraint, string $packageConstrai $information = json_decode($filesystem->read($composerFile), true); foreach ($information['require'] as $dependency => $constraint) { - if (strpos($dependency, 'ext-') === 0) { + if (strpos($dependency, 'ext-') === 0 || $dependency === 'phpseclib/phpseclib') { continue; } diff --git a/src/AdapterTestUtilities/composer.json b/src/AdapterTestUtilities/composer.json index 71de42e1a..872b988e6 100644 --- a/src/AdapterTestUtilities/composer.json +++ b/src/AdapterTestUtilities/composer.json @@ -14,7 +14,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^2.0.0" + "league/flysystem": "^2.0.0 || ^3.0.0" }, "license": "MIT", "authors": [ diff --git a/src/AsyncAwsS3/composer.json b/src/AsyncAwsS3/composer.json index cc865f75e..bcc2c09c0 100644 --- a/src/AsyncAwsS3/composer.json +++ b/src/AsyncAwsS3/composer.json @@ -10,7 +10,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^2.0.0", + "league/flysystem": "^2.0.0 || ^3.0.0", "league/mime-type-detection": "^1.0.0", "async-aws/s3": "^1.5" }, diff --git a/src/AwsS3V3/composer.json b/src/AwsS3V3/composer.json index 827b8abe5..5869864dd 100644 --- a/src/AwsS3V3/composer.json +++ b/src/AwsS3V3/composer.json @@ -10,7 +10,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^2.0.0", + "league/flysystem": "^2.0.0 || ^3.0.0", "league/mime-type-detection": "^1.0.0", "aws/aws-sdk-php": "^3.132.4" }, diff --git a/src/Ftp/composer.json b/src/Ftp/composer.json index 96f056e67..35339e241 100644 --- a/src/Ftp/composer.json +++ b/src/Ftp/composer.json @@ -12,7 +12,7 @@ "require": { "php": "^8.0.2", "ext-ftp": "*", - "league/flysystem": "^2.0.0", + "league/flysystem": "^2.0.0 || ^3.0.0", "league/mime-type-detection": "^1.0.0" }, "license": "MIT", diff --git a/src/GoogleCloudStorage/composer.json b/src/GoogleCloudStorage/composer.json index 951d91b19..cf47df51c 100644 --- a/src/GoogleCloudStorage/composer.json +++ b/src/GoogleCloudStorage/composer.json @@ -12,7 +12,7 @@ "require": { "php": "^8.0.2", "google/cloud-storage": "^1.23", - "league/flysystem": "^2.0.0", + "league/flysystem": "^2.0.0 || ^3.0.0", "league/mime-type-detection": "^1.0.0" }, "license": "MIT", diff --git a/src/InMemory/composer.json b/src/InMemory/composer.json index 6b41dfd0f..aba842930 100644 --- a/src/InMemory/composer.json +++ b/src/InMemory/composer.json @@ -13,7 +13,7 @@ "require": { "php": "^8.0.2", "ext-fileinfo": "*", - "league/flysystem": "^2.0.0" + "league/flysystem": "^2.0.0 || ^3.0.0" }, "license": "MIT", "authors": [ diff --git a/src/PhpseclibV2/composer.json b/src/PhpseclibV2/composer.json index 53a9878f6..7f13e85ea 100644 --- a/src/PhpseclibV2/composer.json +++ b/src/PhpseclibV2/composer.json @@ -9,7 +9,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^2.0.0", + "league/flysystem": "^2.0.0 || ^3.0.0", "league/mime-type-detection": "^1.0.0", "phpseclib/phpseclib": "^2.0" }, diff --git a/src/PhpseclibV3/composer.json b/src/PhpseclibV3/composer.json index f06b3fc35..fdcba2b69 100644 --- a/src/PhpseclibV3/composer.json +++ b/src/PhpseclibV3/composer.json @@ -9,7 +9,7 @@ }, "require": { "php": "^8.0.2", - "league/flysystem": "^2.0.0", + "league/flysystem": "^2.0.0 || ^3.0.0", "league/mime-type-detection": "^1.0.0", "phpseclib/phpseclib": "^3.0" }, diff --git a/src/ZipArchive/composer.json b/src/ZipArchive/composer.json index afd1f0c4a..a15cbf7e3 100644 --- a/src/ZipArchive/composer.json +++ b/src/ZipArchive/composer.json @@ -12,7 +12,7 @@ "require": { "php": "^8.0.2", "ext-zip": "*", - "league/flysystem": "^2.0.0", + "league/flysystem": "^2.0.0 || ^3.0.0", "league/mime-type-detection": "^1.0.0" }, "license": "MIT", From 173d5ab3d5e4396e6e5b9d4a1d782c0e8cd74c3c Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 21:45:58 +0100 Subject: [PATCH 23/46] Run QA on push --- .github/workflows/quality-assurance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality-assurance.yml b/.github/workflows/quality-assurance.yml index dd7c37a89..c38ad604d 100644 --- a/.github/workflows/quality-assurance.yml +++ b/.github/workflows/quality-assurance.yml @@ -6,7 +6,7 @@ on: - src/**/*.php - .github/workflows/quality-assurance.yml branches: - - 2.x + - 3.x pull_request: paths: - src/**/*.php From e134fc13ef888fbffa74f6310a5239b19c7b72c7 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 21:46:30 +0100 Subject: [PATCH 24/46] Publish 3.x subsplits. --- .github/workflows/publish-subsplits.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-subsplits.yml b/.github/workflows/publish-subsplits.yml index 4b0ebdf6d..6709d0040 100644 --- a/.github/workflows/publish-subsplits.yml +++ b/.github/workflows/publish-subsplits.yml @@ -3,13 +3,13 @@ name: Sub-Split Publishing on: push: branches: - - 2.x + - 3.x create: tags: - - '2.*' + - '3.*' delete: tags: - - '2.*' + - '3.*' jobs: publish_subsplits: From 9948a8cb35a72cc26ff5d5596757312e4863d474 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Wed, 12 Jan 2022 21:52:21 +0100 Subject: [PATCH 25/46] Publish 3.x subsplits --- .github/workflows/publish-subsplits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-subsplits.yml b/.github/workflows/publish-subsplits.yml index 6709d0040..5be15d684 100644 --- a/.github/workflows/publish-subsplits.yml +++ b/.github/workflows/publish-subsplits.yml @@ -33,7 +33,7 @@ jobs: key: '${{ runner.os }}-splitsh' - uses: frankdejonge/use-subsplit-publish@1.0.0-beta.3 with: - source-branch: '2.x' + source-branch: '3.x' config-path: './config.subsplit-publish.json' splitsh-path: './.splitsh/splitsh-lite' splitsh-version: 'v1.0.1' From 6257a2ea7cda0c5fd9b779bbea1fd44e3a1fb189 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Thu, 13 Jan 2022 20:53:39 +0100 Subject: [PATCH 26/46] Added has, consolidated the exceptions --- src/AsyncAwsS3/AsyncAwsS3Adapter.php | 4 +- src/AsyncAwsS3/AsyncAwsS3AdapterTest.php | 4 +- src/AwsS3V3/AwsS3V3Adapter.php | 4 +- src/AwsS3V3/AwsS3V3AdapterTest.php | 4 +- src/ExceptionInformationTest.php | 15 +++- src/Filesystem.php | 7 ++ src/FilesystemAdapter.php | 2 + src/FilesystemOperationFailed.php | 2 +- src/FilesystemReader.php | 9 ++- src/MountManager.php | 14 +++- src/MountManagerTest.php | 92 +++++++++++++++++++++++- src/UnableToCheckDirectoryExistence.php | 7 +- src/UnableToCheckExistence.php | 21 ++++++ src/UnableToCheckFileExistence.php | 21 ------ 14 files changed, 166 insertions(+), 40 deletions(-) create mode 100644 src/UnableToCheckExistence.php delete mode 100644 src/UnableToCheckFileExistence.php diff --git a/src/AsyncAwsS3/AsyncAwsS3Adapter.php b/src/AsyncAwsS3/AsyncAwsS3Adapter.php index a29ef4ad2..50ece92dd 100644 --- a/src/AsyncAwsS3/AsyncAwsS3Adapter.php +++ b/src/AsyncAwsS3/AsyncAwsS3Adapter.php @@ -19,7 +19,7 @@ use League\Flysystem\FilesystemAdapter; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; -use League\Flysystem\UnableToCheckFileExistence; +use League\Flysystem\UnableToCheckExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; @@ -124,7 +124,7 @@ public function fileExists(string $path): bool ] )->isSuccess(); } catch (ClientException $e) { - throw UnableToCheckFileExistence::forLocation($path, $e); + throw UnableToCheckExistence::forLocation($path, $e); } } diff --git a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php index 72171c730..c42ace1f8 100644 --- a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php +++ b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php @@ -19,7 +19,7 @@ use League\Flysystem\FileAttributes; use League\Flysystem\FilesystemAdapter; use League\Flysystem\StorageAttributes; -use League\Flysystem\UnableToCheckFileExistence; +use League\Flysystem\UnableToCheckExistence; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToRetrieveMetadata; @@ -198,7 +198,7 @@ public function failing_to_check_for_file_existence(): void $exception = new ClientException(new SimpleMockedResponse()); static::$stubS3Client->throwExceptionWhenExecutingCommand('ObjectExists', $exception); - $this->expectException(UnableToCheckFileExistence::class); + $this->expectException(UnableToCheckExistence::class); $adapter->fileExists('something-that-does-exist.txt'); } diff --git a/src/AwsS3V3/AwsS3V3Adapter.php b/src/AwsS3V3/AwsS3V3Adapter.php index 157eb09a5..1247b3cef 100644 --- a/src/AwsS3V3/AwsS3V3Adapter.php +++ b/src/AwsS3V3/AwsS3V3Adapter.php @@ -15,7 +15,7 @@ use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; use League\Flysystem\UnableToCheckDirectoryExistence; -use League\Flysystem\UnableToCheckFileExistence; +use League\Flysystem\UnableToCheckExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; @@ -128,7 +128,7 @@ public function fileExists(string $path): bool try { return $this->client->doesObjectExist($this->bucket, $this->prefixer->prefixPath($path), $this->options); } catch (Throwable $exception) { - throw UnableToCheckFileExistence::forLocation($path, $exception); + throw UnableToCheckExistence::forLocation($path, $exception); } } diff --git a/src/AwsS3V3/AwsS3V3AdapterTest.php b/src/AwsS3V3/AwsS3V3AdapterTest.php index 4ef2d56e1..5d14e6d96 100644 --- a/src/AwsS3V3/AwsS3V3AdapterTest.php +++ b/src/AwsS3V3/AwsS3V3AdapterTest.php @@ -15,7 +15,7 @@ use League\Flysystem\FilesystemAdapter; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; -use League\Flysystem\UnableToCheckFileExistence; +use League\Flysystem\UnableToCheckExistence; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToRetrieveMetadata; @@ -215,7 +215,7 @@ public function failing_to_check_for_file_existence(): void static::$stubS3Client->throw500ExceptionWhenExecutingCommand('HeadObject'); - $this->expectException(UnableToCheckFileExistence::class); + $this->expectException(UnableToCheckExistence::class); $adapter->fileExists('something-that-does-exist.txt'); } diff --git a/src/ExceptionInformationTest.php b/src/ExceptionInformationTest.php index eb747b4b4..36208f022 100644 --- a/src/ExceptionInformationTest.php +++ b/src/ExceptionInformationTest.php @@ -60,10 +60,19 @@ public function delete_file_exception_information(): void /** * @test */ - public function unable_to_check_file_existence(): void + public function unable_to_check_for_existence(): void { - $exception = UnableToCheckFileExistence::forLocation('location'); - $this->assertEquals(FilesystemOperationFailed::OPERATION_FILE_EXISTS, $exception->operation()); + $exception = UnableToCheckExistence::forLocation('location'); + $this->assertEquals(FilesystemOperationFailed::OPERATION_EXISTENCE_CHECK, $exception->operation()); + } + + /** + * @test + */ + public function unable_to_check_for_directory_existence(): void + { + $exception = UnableToCheckDirectoryExistence::forLocation('location'); + $this->assertEquals(FilesystemOperationFailed::OPERATION_DIRECTORY_EXISTS, $exception->operation()); } /** diff --git a/src/Filesystem.php b/src/Filesystem.php index 58a14b18f..341fea0c2 100644 --- a/src/Filesystem.php +++ b/src/Filesystem.php @@ -41,6 +41,13 @@ public function directoryExists(string $location): bool return $this->adapter->directoryExists($this->pathNormalizer->normalizePath($location)); } + public function has(string $location): bool + { + $path = $this->pathNormalizer->normalizePath($location); + + return $this->adapter->fileExists($path) || $this->adapter->directoryExists($path); + } + public function write(string $location, string $contents, array $config = []): void { $this->adapter->write( diff --git a/src/FilesystemAdapter.php b/src/FilesystemAdapter.php index 41ac68b2a..005710261 100644 --- a/src/FilesystemAdapter.php +++ b/src/FilesystemAdapter.php @@ -8,11 +8,13 @@ interface FilesystemAdapter { /** * @throws FilesystemException + * @throws UnableToCheckExistence */ public function fileExists(string $path): bool; /** * @throws FilesystemException + * @throws UnableToCheckDirectoryExistence */ public function directoryExists(string $path): bool; diff --git a/src/FilesystemOperationFailed.php b/src/FilesystemOperationFailed.php index e0a7fcfa2..9a3ed5697 100644 --- a/src/FilesystemOperationFailed.php +++ b/src/FilesystemOperationFailed.php @@ -8,7 +8,7 @@ interface FilesystemOperationFailed extends FilesystemException { public const OPERATION_WRITE = 'WRITE'; public const OPERATION_UPDATE = 'UPDATE'; - public const OPERATION_FILE_EXISTS = 'FILE_EXISTS'; + public const OPERATION_EXISTENCE_CHECK = 'EXISTENCE_CHECK'; public const OPERATION_DIRECTORY_EXISTS = 'DIRECTORY_EXISTS'; public const OPERATION_CREATE_DIRECTORY = 'CREATE_DIRECTORY'; public const OPERATION_DELETE = 'DELETE'; diff --git a/src/FilesystemReader.php b/src/FilesystemReader.php index 68b6f2199..8fe53c7db 100644 --- a/src/FilesystemReader.php +++ b/src/FilesystemReader.php @@ -15,7 +15,7 @@ interface FilesystemReader /** * @throws FilesystemException - * @throws UnableToCheckFileExistence + * @throws UnableToCheckExistence */ public function fileExists(string $location): bool; @@ -25,6 +25,13 @@ public function fileExists(string $location): bool; */ public function directoryExists(string $location): bool; + /** + * @throws FilesystemException + * @throws UnableToCheckDirectoryExistence + * @throws UnableToCheckExistence + */ + public function has(string $location): bool; + /** * @throws UnableToReadFile * @throws FilesystemException diff --git a/src/MountManager.php b/src/MountManager.php index dbd6dc70c..bb6069fa5 100644 --- a/src/MountManager.php +++ b/src/MountManager.php @@ -33,7 +33,19 @@ public function fileExists(string $location): bool try { return $filesystem->fileExists($path); } catch (Throwable $exception) { - throw UnableToCheckFileExistence::forLocation($location, $exception); + throw UnableToCheckExistence::forLocation($location, $exception); + } + } + + public function has(string $location): bool + { + /** @var FilesystemOperator $filesystem */ + [$filesystem, $path] = $this->determineFilesystemAndPath($location); + + try { + return $filesystem->fileExists($path) || $filesystem->directoryExists($path); + } catch (Throwable $exception) { + throw UnableToCheckExistence::forLocation($location, $exception); } } diff --git a/src/MountManagerTest.php b/src/MountManagerTest.php index 2a694a615..db14fea46 100644 --- a/src/MountManagerTest.php +++ b/src/MountManagerTest.php @@ -6,7 +6,9 @@ use League\Flysystem\InMemory\InMemoryFilesystemAdapter; use PHPUnit\Framework\TestCase; +use function fclose; use function is_resource; +use function stream_get_contents; use function tmpfile; /** @@ -133,7 +135,7 @@ public function dpMetadataRetrieverMethods(): iterable yield 'createDirectory' => ['createDirectory', UnableToCreateDirectory::atLocation('location.txt')]; yield 'read' => ['read', UnableToReadFile::fromLocation('location.txt')]; yield 'readStream' => ['readStream', UnableToReadFile::fromLocation('location.txt')]; - yield 'fileExists' => ['fileExists', UnableToCheckFileExistence::forLocation('location.txt')]; + yield 'fileExists' => ['fileExists', UnableToCheckExistence::forLocation('location.txt')]; } /** @@ -162,6 +164,94 @@ public function reading_a_file_as_a_stream(): void $this->assertEquals('contents', $contents); } + /** + * @test + */ + public function checking_existence_for_an_existing_file(): void + { + $this->secondFilesystem->write('location.txt', 'contents'); + + $existence = $this->mountManager->fileExists('second://location.txt'); + + $this->assertTrue($existence); + } + + /** + * @test + */ + public function checking_existence_for_an_non_existing_file(): void + { + $existence = $this->mountManager->fileExists('second://location.txt'); + + $this->assertFalse($existence); + } + + /** + * @test + */ + public function checking_existence_for_an_non_existing_directory(): void + { + $existence = $this->mountManager->directoryExists('second://some-directory'); + + $this->assertFalse($existence); + } + + /** + * @test + */ + public function checking_existence_for_an_existing_directory(): void + { + $this->secondFilesystem->write('nested/location.txt', 'contents'); + + $existence = $this->mountManager->directoryExists('second://nested'); + + $this->assertTrue($existence); + } + + /** + * @test + */ + public function checking_existence_for_an_existing_file_using_has(): void + { + $this->secondFilesystem->write('location.txt', 'contents'); + + $existence = $this->mountManager->has('second://location.txt'); + + $this->assertTrue($existence); + } + + /** + * @test + */ + public function checking_existence_for_an_non_existing_file_using_has(): void + { + $existence = $this->mountManager->has('second://location.txt'); + + $this->assertFalse($existence); + } + + /** + * @test + */ + public function checking_existence_for_an_non_existing_directory_using_has(): void + { + $existence = $this->mountManager->has('second://some-directory'); + + $this->assertFalse($existence); + } + + /** + * @test + */ + public function checking_existence_for_an_existing_directory_using_has(): void + { + $this->secondFilesystem->write('nested/location.txt', 'contents'); + + $existence = $this->mountManager->has('second://nested'); + + $this->assertTrue($existence); + } + /** * @test */ diff --git a/src/UnableToCheckDirectoryExistence.php b/src/UnableToCheckDirectoryExistence.php index 023799f7c..63abbd2b4 100644 --- a/src/UnableToCheckDirectoryExistence.php +++ b/src/UnableToCheckDirectoryExistence.php @@ -4,14 +4,13 @@ namespace League\Flysystem; -use RuntimeException; use Throwable; -class UnableToCheckDirectoryExistence extends RuntimeException implements FilesystemOperationFailed +class UnableToCheckDirectoryExistence extends UnableToCheckExistence { - public static function forLocation(string $path, Throwable $exception = null): UnableToCheckFileExistence + public static function forLocation(string $path, Throwable $exception = null): UnableToCheckExistence { - return new UnableToCheckFileExistence("Unable to check directory existence for: ${path}", 0, $exception); + return new UnableToCheckDirectoryExistence("Unable to check directory existence for: ${path}", 0, $exception); } public function operation(): string diff --git a/src/UnableToCheckExistence.php b/src/UnableToCheckExistence.php new file mode 100644 index 000000000..be74855c9 --- /dev/null +++ b/src/UnableToCheckExistence.php @@ -0,0 +1,21 @@ + Date: Thu, 13 Jan 2022 21:06:02 +0100 Subject: [PATCH 27/46] Prepare changelog --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da16588a4..6eca6a16c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ -# Version 2.x Changelog +# Changelog + +## 3.0.0 - 2022-01-13 + +### Added + +* FilesystemReader::has to check for directory or file existence +* FilesystemReader::directoryExists to check for directory existence +* FilesystemReader::fileExists to check for file existence +* FilesystemAdapter::directoryExists to check for directory existence +* FilesystemAdapter::fileExists to check for file existence ## 2.4.0 - 2022-01-04 From 0943e286318147dc258ef6d6974afb9c6115546f Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Thu, 13 Jan 2022 21:21:42 +0100 Subject: [PATCH 28/46] Increase badge php version --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0c0b98eaf..2946f1ae7 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/thephpleague/flysystem/blob/master/LICENSE) [![Quality Assurance](https://github.com/thephpleague/flysystem/workflows/Quality%20Assurance/badge.svg?branch=2.x)](https://github.com/thephpleague/flysystem/actions?query=workflow%3A%22Quality+Assurance%22) [![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem.svg)](https://packagist.org/packages/league/flysystem) -![php 7.2+](https://img.shields.io/badge/php-min%207.2-red.svg) +![php 7.2+](https://img.shields.io/badge/php-min%208.0.2-red.svg) ## About Flysystem From cec830e6de78ded362a179d3fd44d5b2b703f8ba Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Thu, 13 Jan 2022 22:11:49 +0100 Subject: [PATCH 29/46] Fix existence exception structure. --- src/AsyncAwsS3/AsyncAwsS3Adapter.php | 4 ++-- src/AsyncAwsS3/AsyncAwsS3AdapterTest.php | 4 ++-- src/AwsS3V3/AwsS3V3Adapter.php | 4 ++-- src/AwsS3V3/AwsS3V3AdapterTest.php | 4 ++-- src/ExceptionInformationTest.php | 9 +++++++++ src/FilesystemAdapter.php | 2 +- src/FilesystemOperationFailed.php | 1 + src/FilesystemReader.php | 3 +-- src/MountManager.php | 2 +- src/MountManagerTest.php | 2 +- src/UnableToCheckDirectoryExistence.php | 4 ++-- src/UnableToCheckExistence.php | 4 ++-- src/UnableToCheckFileExistence.php | 21 +++++++++++++++++++++ 13 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 src/UnableToCheckFileExistence.php diff --git a/src/AsyncAwsS3/AsyncAwsS3Adapter.php b/src/AsyncAwsS3/AsyncAwsS3Adapter.php index 50ece92dd..a29ef4ad2 100644 --- a/src/AsyncAwsS3/AsyncAwsS3Adapter.php +++ b/src/AsyncAwsS3/AsyncAwsS3Adapter.php @@ -19,7 +19,7 @@ use League\Flysystem\FilesystemAdapter; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; -use League\Flysystem\UnableToCheckExistence; +use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; @@ -124,7 +124,7 @@ public function fileExists(string $path): bool ] )->isSuccess(); } catch (ClientException $e) { - throw UnableToCheckExistence::forLocation($path, $e); + throw UnableToCheckFileExistence::forLocation($path, $e); } } diff --git a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php index c42ace1f8..72171c730 100644 --- a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php +++ b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php @@ -19,7 +19,7 @@ use League\Flysystem\FileAttributes; use League\Flysystem\FilesystemAdapter; use League\Flysystem\StorageAttributes; -use League\Flysystem\UnableToCheckExistence; +use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToRetrieveMetadata; @@ -198,7 +198,7 @@ public function failing_to_check_for_file_existence(): void $exception = new ClientException(new SimpleMockedResponse()); static::$stubS3Client->throwExceptionWhenExecutingCommand('ObjectExists', $exception); - $this->expectException(UnableToCheckExistence::class); + $this->expectException(UnableToCheckFileExistence::class); $adapter->fileExists('something-that-does-exist.txt'); } diff --git a/src/AwsS3V3/AwsS3V3Adapter.php b/src/AwsS3V3/AwsS3V3Adapter.php index 1247b3cef..157eb09a5 100644 --- a/src/AwsS3V3/AwsS3V3Adapter.php +++ b/src/AwsS3V3/AwsS3V3Adapter.php @@ -15,7 +15,7 @@ use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; use League\Flysystem\UnableToCheckDirectoryExistence; -use League\Flysystem\UnableToCheckExistence; +use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; @@ -128,7 +128,7 @@ public function fileExists(string $path): bool try { return $this->client->doesObjectExist($this->bucket, $this->prefixer->prefixPath($path), $this->options); } catch (Throwable $exception) { - throw UnableToCheckExistence::forLocation($path, $exception); + throw UnableToCheckFileExistence::forLocation($path, $exception); } } diff --git a/src/AwsS3V3/AwsS3V3AdapterTest.php b/src/AwsS3V3/AwsS3V3AdapterTest.php index 5d14e6d96..4ef2d56e1 100644 --- a/src/AwsS3V3/AwsS3V3AdapterTest.php +++ b/src/AwsS3V3/AwsS3V3AdapterTest.php @@ -15,7 +15,7 @@ use League\Flysystem\FilesystemAdapter; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; -use League\Flysystem\UnableToCheckExistence; +use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToRetrieveMetadata; @@ -215,7 +215,7 @@ public function failing_to_check_for_file_existence(): void static::$stubS3Client->throw500ExceptionWhenExecutingCommand('HeadObject'); - $this->expectException(UnableToCheckExistence::class); + $this->expectException(UnableToCheckFileExistence::class); $adapter->fileExists('something-that-does-exist.txt'); } diff --git a/src/ExceptionInformationTest.php b/src/ExceptionInformationTest.php index 36208f022..a91cbb565 100644 --- a/src/ExceptionInformationTest.php +++ b/src/ExceptionInformationTest.php @@ -57,6 +57,15 @@ public function delete_file_exception_information(): void $this->assertEquals(FilesystemOperationFailed::OPERATION_DELETE, $exception->operation()); } + /** + * @test + */ + public function unable_to_check_for_file_existence(): void + { + $exception = UnableToCheckFileExistence::forLocation('location'); + $this->assertEquals(FilesystemOperationFailed::OPERATION_FILE_EXISTS, $exception->operation()); + } + /** * @test */ diff --git a/src/FilesystemAdapter.php b/src/FilesystemAdapter.php index 005710261..31ffe9fc2 100644 --- a/src/FilesystemAdapter.php +++ b/src/FilesystemAdapter.php @@ -8,7 +8,7 @@ interface FilesystemAdapter { /** * @throws FilesystemException - * @throws UnableToCheckExistence + * @throws UnableToCheckFileExistence */ public function fileExists(string $path): bool; diff --git a/src/FilesystemOperationFailed.php b/src/FilesystemOperationFailed.php index 9a3ed5697..1f61a6c4d 100644 --- a/src/FilesystemOperationFailed.php +++ b/src/FilesystemOperationFailed.php @@ -10,6 +10,7 @@ interface FilesystemOperationFailed extends FilesystemException public const OPERATION_UPDATE = 'UPDATE'; public const OPERATION_EXISTENCE_CHECK = 'EXISTENCE_CHECK'; public const OPERATION_DIRECTORY_EXISTS = 'DIRECTORY_EXISTS'; + public const OPERATION_FILE_EXISTS = 'FILE_EXISTS'; public const OPERATION_CREATE_DIRECTORY = 'CREATE_DIRECTORY'; public const OPERATION_DELETE = 'DELETE'; public const OPERATION_DELETE_DIRECTORY = 'DELETE_DIRECTORY'; diff --git a/src/FilesystemReader.php b/src/FilesystemReader.php index 8fe53c7db..62194d9a9 100644 --- a/src/FilesystemReader.php +++ b/src/FilesystemReader.php @@ -15,7 +15,7 @@ interface FilesystemReader /** * @throws FilesystemException - * @throws UnableToCheckExistence + * @throws UnableToCheckFileExistence */ public function fileExists(string $location): bool; @@ -27,7 +27,6 @@ public function directoryExists(string $location): bool; /** * @throws FilesystemException - * @throws UnableToCheckDirectoryExistence * @throws UnableToCheckExistence */ public function has(string $location): bool; diff --git a/src/MountManager.php b/src/MountManager.php index bb6069fa5..3dfd93e25 100644 --- a/src/MountManager.php +++ b/src/MountManager.php @@ -33,7 +33,7 @@ public function fileExists(string $location): bool try { return $filesystem->fileExists($path); } catch (Throwable $exception) { - throw UnableToCheckExistence::forLocation($location, $exception); + throw UnableToCheckFileExistence::forLocation($location, $exception); } } diff --git a/src/MountManagerTest.php b/src/MountManagerTest.php index db14fea46..0469f6ed5 100644 --- a/src/MountManagerTest.php +++ b/src/MountManagerTest.php @@ -135,7 +135,7 @@ public function dpMetadataRetrieverMethods(): iterable yield 'createDirectory' => ['createDirectory', UnableToCreateDirectory::atLocation('location.txt')]; yield 'read' => ['read', UnableToReadFile::fromLocation('location.txt')]; yield 'readStream' => ['readStream', UnableToReadFile::fromLocation('location.txt')]; - yield 'fileExists' => ['fileExists', UnableToCheckExistence::forLocation('location.txt')]; + yield 'fileExists' => ['fileExists', UnableToCheckFileExistence::forLocation('location.txt')]; } /** diff --git a/src/UnableToCheckDirectoryExistence.php b/src/UnableToCheckDirectoryExistence.php index 63abbd2b4..a935f012b 100644 --- a/src/UnableToCheckDirectoryExistence.php +++ b/src/UnableToCheckDirectoryExistence.php @@ -8,9 +8,9 @@ class UnableToCheckDirectoryExistence extends UnableToCheckExistence { - public static function forLocation(string $path, Throwable $exception = null): UnableToCheckExistence + public static function forLocation(string $path, Throwable $exception = null): static { - return new UnableToCheckDirectoryExistence("Unable to check directory existence for: ${path}", 0, $exception); + return new static("Unable to check directory existence for: ${path}", 0, $exception); } public function operation(): string diff --git a/src/UnableToCheckExistence.php b/src/UnableToCheckExistence.php index be74855c9..c70561bce 100644 --- a/src/UnableToCheckExistence.php +++ b/src/UnableToCheckExistence.php @@ -9,9 +9,9 @@ class UnableToCheckExistence extends RuntimeException implements FilesystemOperationFailed { - public static function forLocation(string $path, Throwable $exception = null): UnableToCheckExistence + public static function forLocation(string $path, Throwable $exception = null): static { - return new UnableToCheckExistence("Unable to check existence for: ${path}", 0, $exception); + return new static("Unable to check existence for: ${path}", 0, $exception); } public function operation(): string diff --git a/src/UnableToCheckFileExistence.php b/src/UnableToCheckFileExistence.php new file mode 100644 index 000000000..ba494b83e --- /dev/null +++ b/src/UnableToCheckFileExistence.php @@ -0,0 +1,21 @@ + Date: Thu, 13 Jan 2022 22:17:55 +0100 Subject: [PATCH 30/46] Remove duplicate methods --- src/FilesystemAdapter.php | 4 ++-- src/FilesystemReader.php | 4 ++-- src/UnableToCheckDirectoryExistence.php | 7 ------- src/UnableToCheckFileExistence.php | 8 -------- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/FilesystemAdapter.php b/src/FilesystemAdapter.php index 31ffe9fc2..714309f31 100644 --- a/src/FilesystemAdapter.php +++ b/src/FilesystemAdapter.php @@ -8,13 +8,13 @@ interface FilesystemAdapter { /** * @throws FilesystemException - * @throws UnableToCheckFileExistence + * @throws UnableToCheckExistence */ public function fileExists(string $path): bool; /** * @throws FilesystemException - * @throws UnableToCheckDirectoryExistence + * @throws UnableToCheckExistence */ public function directoryExists(string $path): bool; diff --git a/src/FilesystemReader.php b/src/FilesystemReader.php index 62194d9a9..d08743f92 100644 --- a/src/FilesystemReader.php +++ b/src/FilesystemReader.php @@ -15,13 +15,13 @@ interface FilesystemReader /** * @throws FilesystemException - * @throws UnableToCheckFileExistence + * @throws UnableToCheckExistence */ public function fileExists(string $location): bool; /** * @throws FilesystemException - * @throws UnableToCheckDirectoryExistence + * @throws UnableToCheckExistence */ public function directoryExists(string $location): bool; diff --git a/src/UnableToCheckDirectoryExistence.php b/src/UnableToCheckDirectoryExistence.php index a935f012b..73ce858b9 100644 --- a/src/UnableToCheckDirectoryExistence.php +++ b/src/UnableToCheckDirectoryExistence.php @@ -4,15 +4,8 @@ namespace League\Flysystem; -use Throwable; - class UnableToCheckDirectoryExistence extends UnableToCheckExistence { - public static function forLocation(string $path, Throwable $exception = null): static - { - return new static("Unable to check directory existence for: ${path}", 0, $exception); - } - public function operation(): string { return FilesystemOperationFailed::OPERATION_DIRECTORY_EXISTS; diff --git a/src/UnableToCheckFileExistence.php b/src/UnableToCheckFileExistence.php index ba494b83e..bc0536dcb 100644 --- a/src/UnableToCheckFileExistence.php +++ b/src/UnableToCheckFileExistence.php @@ -4,16 +4,8 @@ namespace League\Flysystem; -use RuntimeException; -use Throwable; - class UnableToCheckFileExistence extends UnableToCheckExistence { - public static function forLocation(string $path, Throwable $exception = null): static - { - return new static("Unable to check existence for: ${path}", 0, $exception); - } - public function operation(): string { return FilesystemOperationFailed::OPERATION_FILE_EXISTS; From 1d14bb94e971004083927019b6d0e28726eddfb3 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Thu, 13 Jan 2022 22:23:39 +0100 Subject: [PATCH 31/46] Convert exceptions --- src/AsyncAwsS3/AsyncAwsS3Adapter.php | 13 +++++++++---- .../GoogleCloudStorageAdapter.php | 14 ++++++++++++-- src/PhpseclibV2/SftpAdapter.php | 14 ++++++++++++-- src/PhpseclibV3/SftpAdapter.php | 14 ++++++++++++-- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/AsyncAwsS3/AsyncAwsS3Adapter.php b/src/AsyncAwsS3/AsyncAwsS3Adapter.php index a29ef4ad2..7737bdf1f 100644 --- a/src/AsyncAwsS3/AsyncAwsS3Adapter.php +++ b/src/AsyncAwsS3/AsyncAwsS3Adapter.php @@ -19,6 +19,7 @@ use League\Flysystem\FilesystemAdapter; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; +use League\Flysystem\UnableToCheckDirectoryExistence; use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToDeleteFile; @@ -124,7 +125,7 @@ public function fileExists(string $path): bool ] )->isSuccess(); } catch (ClientException $e) { - throw UnableToCheckFileExistence::forLocation($path, $e); + throw UnableToCheckFileExistence:: forLocation($path, $e); } } @@ -258,10 +259,14 @@ public function fileSize(string $path): FileAttributes public function directoryExists(string $path): bool { - $prefix = $this->prefixer->prefixDirectoryPath($path); - $options = ['Bucket' => $this->bucket, 'Prefix' => $prefix, 'Delimiter' => '/']; + try { + $prefix = $this->prefixer->prefixDirectoryPath($path); + $options = ['Bucket' => $this->bucket, 'Prefix' => $prefix, 'Delimiter' => '/']; - return $this->client->listObjectsV2($options)->getKeyCount() > 0; + return $this->client->listObjectsV2($options)->getKeyCount() > 0; + } catch (Throwable $exception) { + throw UnableToCheckDirectoryExistence::forLocation($path, $exception); + } } public function listContents(string $path, bool $deep): iterable diff --git a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php index 272d843c2..764d6c293 100644 --- a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php +++ b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php @@ -14,6 +14,8 @@ use League\Flysystem\FilesystemException; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; +use League\Flysystem\UnableToCheckDirectoryExistence; +use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToDeleteDirectory; use League\Flysystem\UnableToDeleteFile; @@ -59,14 +61,22 @@ public function fileExists(string $path): bool { $prefixedPath = $this->prefixer->prefixPath($path); - return $this->bucket->object($prefixedPath)->exists(); + try { + return $this->bucket->object($prefixedPath)->exists(); + } catch (Throwable $exception) { + UnableToCheckFileExistence::forLocation($path); + } } public function directoryExists(string $path): bool { $prefixedPath = $this->prefixer->prefixDirectoryPath($path); - return $this->bucket->object($prefixedPath)->exists(); + try { + return $this->bucket->object($prefixedPath)->exists(); + } catch (Throwable $exception) { + UnableToCheckDirectoryExistence::forLocation($path); + } } public function write(string $path, string $contents, Config $config): void diff --git a/src/PhpseclibV2/SftpAdapter.php b/src/PhpseclibV2/SftpAdapter.php index c595f513f..aa2cf8049 100644 --- a/src/PhpseclibV2/SftpAdapter.php +++ b/src/PhpseclibV2/SftpAdapter.php @@ -11,6 +11,8 @@ use League\Flysystem\FilesystemException; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; +use League\Flysystem\UnableToCheckDirectoryExistence; +use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToCreateDirectory; use League\Flysystem\UnableToMoveFile; @@ -63,14 +65,22 @@ public function fileExists(string $path): bool { $location = $this->prefixer->prefixPath($path); - return $this->connectionProvider->provideConnection()->is_file($location); + try { + return $this->connectionProvider->provideConnection()->is_file($location); + } catch (Throwable $exception) { + throw UnableToCheckFileExistence::forLocation($path, $exception); + } } public function directoryExists(string $path): bool { $location = $this->prefixer->prefixDirectoryPath($path); - return $this->connectionProvider->provideConnection()->is_dir($location); + try { + return $this->connectionProvider->provideConnection()->is_dir($location); + } catch (Throwable $exception) { + throw UnableToCheckDirectoryExistence::forLocation($path, $exception); + } } /** diff --git a/src/PhpseclibV3/SftpAdapter.php b/src/PhpseclibV3/SftpAdapter.php index 2cc94523f..e48cbaf07 100644 --- a/src/PhpseclibV3/SftpAdapter.php +++ b/src/PhpseclibV3/SftpAdapter.php @@ -11,6 +11,8 @@ use League\Flysystem\FilesystemException; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; +use League\Flysystem\UnableToCheckDirectoryExistence; +use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToCreateDirectory; use League\Flysystem\UnableToMoveFile; @@ -63,14 +65,22 @@ public function fileExists(string $path): bool { $location = $this->prefixer->prefixPath($path); - return $this->connectionProvider->provideConnection()->is_file($location); + try { + return $this->connectionProvider->provideConnection()->is_file($location); + } catch (Throwable $exception) { + throw UnableToCheckFileExistence::forLocation($path, $exception); + } } public function directoryExists(string $path): bool { $location = $this->prefixer->prefixDirectoryPath($path); - return $this->connectionProvider->provideConnection()->is_dir($location); + try { + return $this->connectionProvider->provideConnection()->is_dir($location); + } catch (Throwable $exception) { + throw UnableToCheckDirectoryExistence::forLocation($path, $exception); + } } /** From cd9b487888fb33e25595d62110f14681a9e4b434 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Thu, 13 Jan 2022 22:24:17 +0100 Subject: [PATCH 32/46] Add ext-zip to require-dev --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index e090459ef..1df9589c0 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "league/mime-type-detection": "^1.0.0" }, "require-dev": { + "ext-zip": "*", "ext-fileinfo": "*", "phpunit/phpunit": "^9.5.11", "phpstan/phpstan": "^0.12.26", From 18926d0ce8eed130aec421c9376503a88523322b Mon Sep 17 00:00:00 2001 From: simialbi Date: Fri, 7 Jan 2022 14:47:28 +0100 Subject: [PATCH 33/46] Translate pahts for directories too Translate paths for directories... --- src/Local/LocalFilesystemAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Local/LocalFilesystemAdapter.php b/src/Local/LocalFilesystemAdapter.php index 23d4f0071..1b4ddaad8 100644 --- a/src/Local/LocalFilesystemAdapter.php +++ b/src/Local/LocalFilesystemAdapter.php @@ -213,7 +213,7 @@ public function listContents(string $path, bool $deep): iterable $permissions = octdec(substr(sprintf('%o', $fileInfo->getPerms()), -4)); $visibility = $isDirectory ? $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions); - yield $isDirectory ? new DirectoryAttributes($path, $visibility, $lastModified) : new FileAttributes( + yield $isDirectory ? new DirectoryAttributes(str_replace('\\', '/', $path), $visibility, $lastModified) : new FileAttributes( str_replace('\\', '/', $path), $fileInfo->getSize(), $visibility, From 70483898cf93bc8f601cd20ef751a757f19d6394 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 14 Jan 2022 20:43:53 +0100 Subject: [PATCH 34/46] Remove accidental space --- src/AsyncAwsS3/AsyncAwsS3Adapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsyncAwsS3/AsyncAwsS3Adapter.php b/src/AsyncAwsS3/AsyncAwsS3Adapter.php index 7737bdf1f..d1abb5e72 100644 --- a/src/AsyncAwsS3/AsyncAwsS3Adapter.php +++ b/src/AsyncAwsS3/AsyncAwsS3Adapter.php @@ -125,7 +125,7 @@ public function fileExists(string $path): bool ] )->isSuccess(); } catch (ClientException $e) { - throw UnableToCheckFileExistence:: forLocation($path, $e); + throw UnableToCheckFileExistence::forLocation($path, $e); } } From 27bad877dfafcadcd346c0a40a1161fb1ace7e9d Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 14 Jan 2022 20:54:22 +0100 Subject: [PATCH 35/46] Retry on async aws test failure. --- src/AsyncAwsS3/AsyncAwsS3AdapterTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php index 72171c730..72f20145e 100644 --- a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php +++ b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php @@ -6,6 +6,7 @@ use AsyncAws\Core\Exception\Http\ClientException; +use AsyncAws\Core\Exception\Http\NetworkException; use AsyncAws\Core\Test\Http\SimpleMockedResponse; use AsyncAws\Core\Test\ResultMockFactory; @@ -18,6 +19,7 @@ use League\Flysystem\Config; use League\Flysystem\FileAttributes; use League\Flysystem\FilesystemAdapter; +use League\Flysystem\Ftp\UnableToConnectToFtpHost; use League\Flysystem\StorageAttributes; use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToDeleteFile; @@ -49,6 +51,12 @@ class AsyncAwsS3AdapterTest extends FilesystemAdapterTestCase */ private static $stubS3Client; + protected function setUp(): void + { + parent::setUp(); + $this->retryOnException(NetworkException::class); + } + public static function setUpBeforeClass(): void { static::$adapterPrefix = 'ci/' . bin2hex(random_bytes(10)); From 8398fccd8d0de7c1b90699bebbfa979e65a4a324 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 14 Jan 2022 23:33:54 +0100 Subject: [PATCH 36/46] Added original exceptions for better stack traces --- src/GoogleCloudStorage/GoogleCloudStorageAdapter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php index 764d6c293..9d7ef21a9 100644 --- a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php +++ b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php @@ -64,7 +64,7 @@ public function fileExists(string $path): bool try { return $this->bucket->object($prefixedPath)->exists(); } catch (Throwable $exception) { - UnableToCheckFileExistence::forLocation($path); + UnableToCheckFileExistence::forLocation($path, $exception); } } @@ -75,7 +75,7 @@ public function directoryExists(string $path): bool try { return $this->bucket->object($prefixedPath)->exists(); } catch (Throwable $exception) { - UnableToCheckDirectoryExistence::forLocation($path); + UnableToCheckDirectoryExistence::forLocation($path, $exception); } } From 1e1d27aff09e9bf996d088ef35b80f958fa9692a Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 14 Jan 2022 23:34:16 +0100 Subject: [PATCH 37/46] Check for valid resoure before closing them in test case. --- src/AdapterTestUtilities/FilesystemAdapterTestCase.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AdapterTestUtilities/FilesystemAdapterTestCase.php b/src/AdapterTestUtilities/FilesystemAdapterTestCase.php index 5ff70717f..e64f88786 100644 --- a/src/AdapterTestUtilities/FilesystemAdapterTestCase.php +++ b/src/AdapterTestUtilities/FilesystemAdapterTestCase.php @@ -4,6 +4,7 @@ namespace League\Flysystem\AdapterTestUtilities; +use function is_resource; use const PHP_EOL; use Generator; use League\Flysystem\Config; @@ -144,7 +145,7 @@ public function writing_a_file_with_a_stream(): void $writeStream = stream_with_contents('contents'); $adapter->writeStream('path.txt', $writeStream, new Config()); - fclose($writeStream); + is_resource($writeStream) && fclose($writeStream); $fileExists = $adapter->fileExists('path.txt'); $this->assertTrue($fileExists); @@ -195,7 +196,7 @@ public function writing_a_file_with_an_empty_stream(): void $writeStream = stream_with_contents(''); $adapter->writeStream('path.txt', $writeStream, new Config()); - fclose($writeStream); + is_resource($writeStream) && fclose($writeStream); $fileExists = $adapter->fileExists('path.txt'); $this->assertTrue($fileExists); From bbd6b71db5867beb6ba97f9bc02c6497d6aa9071 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 14 Jan 2022 23:34:45 +0100 Subject: [PATCH 38/46] Add missing throw statement --- src/GoogleCloudStorage/GoogleCloudStorageAdapter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php index 9d7ef21a9..760e09fe8 100644 --- a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php +++ b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php @@ -64,7 +64,7 @@ public function fileExists(string $path): bool try { return $this->bucket->object($prefixedPath)->exists(); } catch (Throwable $exception) { - UnableToCheckFileExistence::forLocation($path, $exception); + throw UnableToCheckFileExistence::forLocation($path, $exception); } } @@ -75,7 +75,7 @@ public function directoryExists(string $path): bool try { return $this->bucket->object($prefixedPath)->exists(); } catch (Throwable $exception) { - UnableToCheckDirectoryExistence::forLocation($path, $exception); + throw UnableToCheckDirectoryExistence::forLocation($path, $exception); } } From 290ba2999691ea48cad14297551e15b665382c86 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 14 Jan 2022 23:54:13 +0100 Subject: [PATCH 39/46] Check existence of directory using a listing. --- .../GoogleCloudStorageAdapter.php | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php index 760e09fe8..a0d63236d 100644 --- a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php +++ b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php @@ -11,7 +11,6 @@ use League\Flysystem\DirectoryAttributes; use League\Flysystem\FileAttributes; use League\Flysystem\FilesystemAdapter; -use League\Flysystem\FilesystemException; use League\Flysystem\PathPrefixer; use League\Flysystem\StorageAttributes; use League\Flysystem\UnableToCheckDirectoryExistence; @@ -27,6 +26,12 @@ use League\Flysystem\Visibility; use Throwable; +use function array_key_exists; +use function count; +use function rtrim; +use function sprintf; +use function strlen; + class GoogleCloudStorageAdapter implements FilesystemAdapter { /** @@ -49,8 +54,12 @@ class GoogleCloudStorageAdapter implements FilesystemAdapter */ private $defaultVisibility; - public function __construct(Bucket $bucket, string $prefix = '', VisibilityHandler $visibilityHandler = null, string $defaultVisibility = Visibility::PRIVATE) - { + public function __construct( + Bucket $bucket, + string $prefix = '', + VisibilityHandler $visibilityHandler = null, + string $defaultVisibility = Visibility::PRIVATE + ) { $this->bucket = $bucket; $this->prefixer = new PathPrefixer($prefix); $this->visibilityHandler = $visibilityHandler ?: new PortableVisibilityHandler(); @@ -70,13 +79,32 @@ public function fileExists(string $path): bool public function directoryExists(string $path): bool { - $prefixedPath = $this->prefixer->prefixDirectoryPath($path); + $prefixedPath = $this->prefixer->prefixPath($path); + $options = [ + 'delimiter' => '/', + 'includeTrailingDelimiter' => true, + ]; + + if (strlen($prefixedPath) > 0) { + $options = ['prefix' => rtrim($prefixedPath, '/') . '/']; + } try { - return $this->bucket->object($prefixedPath)->exists(); + $objects = $this->bucket->objects($options); } catch (Throwable $exception) { throw UnableToCheckDirectoryExistence::forLocation($path, $exception); } + + if (count($objects->prefixes()) > 0) { + return true; + } + + /** @var StorageObject $object */ + foreach ($objects as $object) { + return true; + } + + return false; } public function write(string $path, string $contents, Config $config): void @@ -127,9 +155,7 @@ public function readStream(string $path) $prefixedPath = $this->prefixer->prefixPath($path); try { - $stream = $this->bucket->object($prefixedPath) - ->downloadAsStream() - ->detach(); + $stream = $this->bucket->object($prefixedPath)->downloadAsStream()->detach(); } catch (Throwable $exception) { throw UnableToReadFile::fromLocation($path, '', $exception); } @@ -138,6 +164,7 @@ public function readStream(string $path) if ( ! is_resource($stream)) { throw UnableToReadFile::fromLocation($path, 'Downloaded object does not contain a file resource.'); } + // @codeCoverageIgnoreEnd return $stream; From ea4542acf038d2ac37fa36df6a29a25f6dc41a63 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Sat, 15 Jan 2022 11:38:54 +0100 Subject: [PATCH 40/46] Refamped GCS stubbing --- .../FilesystemAdapterTestCase.php | 18 +++-- .../GoogleCloudStorageAdapter.php | 11 ++- .../GoogleCloudStorageAdapterTest.php | 39 +++++----- src/GoogleCloudStorage/StubBucket.php | 64 ---------------- src/GoogleCloudStorage/StubObject.php | 73 ------------------- src/GoogleCloudStorage/StubRiggedBucket.php | 55 ++++++++++++++ src/GoogleCloudStorage/StubStorageClient.php | 22 ++++-- src/PathPrefixer.php | 2 +- 8 files changed, 113 insertions(+), 171 deletions(-) delete mode 100644 src/GoogleCloudStorage/StubBucket.php delete mode 100644 src/GoogleCloudStorage/StubObject.php create mode 100644 src/GoogleCloudStorage/StubRiggedBucket.php diff --git a/src/AdapterTestUtilities/FilesystemAdapterTestCase.php b/src/AdapterTestUtilities/FilesystemAdapterTestCase.php index e64f88786..aef79b383 100644 --- a/src/AdapterTestUtilities/FilesystemAdapterTestCase.php +++ b/src/AdapterTestUtilities/FilesystemAdapterTestCase.php @@ -5,6 +5,7 @@ namespace League\Flysystem\AdapterTestUtilities; use function is_resource; +use function iterator_to_array; use const PHP_EOL; use Generator; use League\Flysystem\Config; @@ -36,7 +37,7 @@ abstract class FilesystemAdapterTestCase extends TestCase /** * @var bool */ - private $isUsingCustomAdapter = false; + protected $isUsingCustomAdapter = false; public static function clearFilesystemAdapterCache(): void { @@ -78,8 +79,8 @@ protected function useAdapter(FilesystemAdapter $adapter): FilesystemAdapter */ public function cleanupAdapter(): void { - $this->clearStorage(); $this->clearCustomAdapter(); + $this->clearStorage(); } public function clearStorage(): void @@ -331,6 +332,10 @@ public function checking_if_a_directory_exists_after_creating_it(): void $adapter = $this->adapter(); $adapter->createDirectory('explicitly-created-directory', new Config()); self::assertTrue($adapter->directoryExists('explicitly-created-directory')); + $adapter->deleteDirectory('explicitly-created-directory'); + $l = iterator_to_array($adapter->listContents('/', false), false); + self::assertEquals([], $l); + self::assertFalse($adapter->directoryExists('explicitly-created-directory')); }); } @@ -718,17 +723,18 @@ public function creating_a_directory(): void $this->runScenario(function () { $adapter = $this->adapter(); - $adapter->createDirectory('path', new Config()); + $adapter->createDirectory('creating_a_directory/path', new Config()); // Creating a directory should be idempotent. - $adapter->createDirectory('path', new Config()); + $adapter->createDirectory('creating_a_directory/path', new Config()); - $contents = iterator_to_array($adapter->listContents('', false)); + $contents = iterator_to_array($adapter->listContents('creating_a_directory', false)); $this->assertCount(1, $contents, $this->formatIncorrectListingCount($contents)); /** @var DirectoryAttributes $directory */ $directory = $contents[0]; $this->assertInstanceOf(DirectoryAttributes::class, $directory); - $this->assertEquals('path', $directory->path()); + $this->assertEquals('creating_a_directory/path', $directory->path()); + $adapter->deleteDirectory('creating_a_directory/path'); }); } diff --git a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php index a0d63236d..0acf5211f 100644 --- a/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php +++ b/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php @@ -191,6 +191,10 @@ public function deleteDirectory(string $path): void foreach ($listing as $attributes) { $this->delete($attributes->path()); } + + if ($path !== '') { + $this->delete(rtrim($path, '/') . '/'); + } } catch (Throwable $exception) { throw UnableToDeleteDirectory::atLocation($path, '', $exception); } @@ -198,8 +202,11 @@ public function deleteDirectory(string $path): void public function createDirectory(string $path, Config $config): void { - $prefixedPath = rtrim($this->prefixer->prefixPath($path), '/') . '/'; - $this->bucket->upload('', ['name' => $prefixedPath]); + $prefixedPath = $this->prefixer->prefixDirectoryPath($path); + + if ($prefixedPath !== '') { + $this->bucket->upload('', ['name' => $prefixedPath]); + } } public function setVisibility(string $path, string $visibility): void diff --git a/src/GoogleCloudStorage/GoogleCloudStorageAdapterTest.php b/src/GoogleCloudStorage/GoogleCloudStorageAdapterTest.php index f86ff6777..cd563c70b 100644 --- a/src/GoogleCloudStorage/GoogleCloudStorageAdapterTest.php +++ b/src/GoogleCloudStorage/GoogleCloudStorageAdapterTest.php @@ -7,6 +7,7 @@ use League\Flysystem\AdapterTestUtilities\FilesystemAdapterTestCase; use League\Flysystem\Config; use League\Flysystem\FilesystemAdapter; +use League\Flysystem\PathPrefixer; use League\Flysystem\UnableToDeleteDirectory; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToRetrieveMetadata; @@ -21,15 +22,23 @@ class GoogleCloudStorageAdapterTest extends FilesystemAdapterTestCase * @var string */ private static $adapterPrefix = 'ci'; - - /** - * @var StubBucket - */ - private static $bucket; + private static StubRiggedBucket $bucket; + private static PathPrefixer $prefixer; public static function setUpBeforeClass(): void { - static::$adapterPrefix = 'ci/' . bin2hex(random_bytes(10)); + static::$adapterPrefix = 'frank-ci'; // . bin2hex(random_bytes(10)); + static::$prefixer = new PathPrefixer(static::$adapterPrefix); + } + + public function prefixPath(string $path): string + { + return static::$prefixer->prefixPath($path); + } + + public function prefixDirectoryPath(string $path): string + { + return static::$prefixer->prefixDirectoryPath($path); } protected static function createFilesystemAdapter(): FilesystemAdapter @@ -44,12 +53,7 @@ protected static function createFilesystemAdapter(): FilesystemAdapter ]; $storageClient = new StubStorageClient($clientOptions); - $connection = $storageClient->connection(); - $projectId = $storageClient->projectId(); - - static::$bucket = $bucket = new StubBucket($connection, 'flysystem', [ - 'requesterProjectId' => $projectId, - ]); + static::$bucket = $bucket = $storageClient->bucket('flysystem'); return new GoogleCloudStorageAdapter($bucket, static::$adapterPrefix); } @@ -62,7 +66,7 @@ public function fetching_visibility_of_non_existing_file(): void $this->markTestSkipped(" Not relevant for this adapter since it's a missing ACL, which turns into a 404 which is the expected outcome - of a private visibility. 🤷‍♂️ + of a private visibility. ¯\_(ツ)_/¯ "); } @@ -89,7 +93,7 @@ public function listing_a_toplevel_directory(): void public function failing_to_write_a_file(): void { $adapter = $this->adapter(); - static::$bucket->failOnUpload(); + static::$bucket->failForUpload($this->prefixPath('something.txt')); $this->expectException(UnableToWriteFile::class); @@ -102,7 +106,7 @@ public function failing_to_write_a_file(): void public function failing_to_delete_a_file(): void { $adapter = $this->adapter(); - static::$bucket->withObject(static::$adapterPrefix . '/filename.txt')->failWhenDeleting(); + static::$bucket->failForObject($this->prefixPath('filename.txt')); $this->expectException(UnableToDeleteFile::class); @@ -116,7 +120,8 @@ public function failing_to_delete_a_directory(): void { $adapter = $this->adapter(); $this->givenWeHaveAnExistingFile('dir/filename.txt'); - static::$bucket->withObject(static::$adapterPrefix . '/dir/filename.txt')->failWhenDeleting(); + + static::$bucket->failForObject($this->prefixPath('dir/filename.txt')); $this->expectException(UnableToDeleteDirectory::class); @@ -129,7 +134,7 @@ public function failing_to_delete_a_directory(): void public function failing_to_retrieve_visibility(): void { $adapter = $this->adapter(); - static::$bucket->withObject(static::$adapterPrefix . '/filename.txt')->failWhenAccessingAcl(); + static::$bucket->failForObject($this->prefixPath('filename.txt')); $this->expectException(UnableToRetrieveMetadata::class); diff --git a/src/GoogleCloudStorage/StubBucket.php b/src/GoogleCloudStorage/StubBucket.php deleted file mode 100644 index bcd2d5d1c..000000000 --- a/src/GoogleCloudStorage/StubBucket.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ - private $stubbedObjects; - - /** - * @var bool - */ - private $shouldFailOnUpload = false; - - public function __construct(ConnectionInterface $connection, $name, array $info = []) - { - parent::__construct($connection, $name, $info); - $this->theConnection = $connection; - } - - public function withObject(string $path): StubObject - { - return $this->stubbedObjects[$path] = new StubObject($this->theConnection, $path, '', parent::object($path)); - } - - public function object($name, array $options = []) - { - $object = $this->stubbedObjects[$name] ?? parent::object($name, $options); - unset($this->stubbedObjects[$name]); - - return $object; - } - - public function failOnUpload(): void - { - $this->shouldFailOnUpload = true; - } - - public function upload($data, array $options = []) - { - if ($this->shouldFailOnUpload) { - $this->shouldFailOnUpload = false; - throw new LogicException("Oh no!"); - } - - return parent::upload($data, $options); - } -} diff --git a/src/GoogleCloudStorage/StubObject.php b/src/GoogleCloudStorage/StubObject.php deleted file mode 100644 index 8b9781a3c..000000000 --- a/src/GoogleCloudStorage/StubObject.php +++ /dev/null @@ -1,73 +0,0 @@ -storageObject = $storageObject; - } - - public function failWhenAccessingAcl(): void - { - $this->shouldFailWhenAccessingAcl = true; - } - - public function acl() - { - if ($this->shouldFailWhenAccessingAcl) { - $this->shouldFailWhenAccessingAcl = false; - throw new LogicException("Something bad happened! Oh no!"); - } - - return $this->storageObject->acl(); - } - - public function failWhenDeleting(): void - { - $this->shouldFailWhenDeleting = true; - } - - /** - * @param array $options - */ - public function delete(array $options = []) - { - if ($this->shouldFailWhenDeleting) { - $this->shouldFailWhenDeleting = false; - throw new LogicException("Oh no!"); - } - - parent::delete($options); - } -} diff --git a/src/GoogleCloudStorage/StubRiggedBucket.php b/src/GoogleCloudStorage/StubRiggedBucket.php new file mode 100644 index 000000000..024e9454a --- /dev/null +++ b/src/GoogleCloudStorage/StubRiggedBucket.php @@ -0,0 +1,55 @@ +setupTrigger('object', $name, $throwable); + } + + public function failForUpload($name, ?Throwable $throwable = null): void + { + $this->setupTrigger('upload', $name, $throwable); + } + + public function object($name, array $options = []) + { + $this->pushTrigger('object', $name); + + return parent::object($name, $options); + } + + public function upload($data, array $options = []) + { + $this->pushTrigger('upload', $options['name'] ?? 'unknown-object-name'); + + return parent::upload($data, $options); + } + + private function setupTrigger(string $method, string $name, ?Throwable $throwable): void + { + $this->triggers[$method][$name] = $throwable ?: new LogicException('unknown error'); + } + + private function pushTrigger(string $method, string $name): void + { + $trigger = $this->triggers[$method][$name] ?? null; + + if ($trigger instanceof Throwable) { + unset($this->triggers[$method][$name]); + throw $trigger; + } + } +} diff --git a/src/GoogleCloudStorage/StubStorageClient.php b/src/GoogleCloudStorage/StubStorageClient.php index cc80dc8ec..1cad84b9f 100644 --- a/src/GoogleCloudStorage/StubStorageClient.php +++ b/src/GoogleCloudStorage/StubStorageClient.php @@ -4,23 +4,29 @@ namespace League\Flysystem\GoogleCloudStorage; -use Google\Cloud\Storage\Connection\ConnectionInterface; use Google\Cloud\Storage\StorageClient; class StubStorageClient extends StorageClient { + private ?StubRiggedBucket $riggedBucket = null; + + public function __construct(array $config = []) + { + parent::__construct($config); + } + /** * @var string|null */ protected $projectId; - public function connection(): ConnectionInterface - { - return $this->connection; - } - - public function projectId(): ?string + public function bucket($name, $userProject = false) { - return $this->projectId; + if ($name === 'flysystem' && ! $this->riggedBucket) { + $this->riggedBucket = new StubRiggedBucket($this->connection, 'flysystem', [ + 'requesterProjectId' => $this->projectId, + ]); + } + return $name === 'flysystem' ? $this->riggedBucket : parent::bucket($name, $userProject); } } diff --git a/src/PathPrefixer.php b/src/PathPrefixer.php index b675b8e5c..2a9cd45d0 100644 --- a/src/PathPrefixer.php +++ b/src/PathPrefixer.php @@ -51,7 +51,7 @@ public function prefixDirectoryPath(string $path): string { $prefixedPath = $this->prefixPath(rtrim($path, '\\/')); - if ((substr($prefixedPath, -1) === $this->separator) || $prefixedPath === '') { + if ($prefixedPath === '' || substr($prefixedPath, -1) === $this->separator) { return $prefixedPath; } From 03592e78fe4a0e76bc1e1ad5094d8d1b45d03a36 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Sat, 15 Jan 2022 11:54:12 +0100 Subject: [PATCH 41/46] Also delete parent directory in ZipArchive implementation --- src/ZipArchive/ZipArchiveAdapter.php | 3 +-- src/ZipArchive/ZipArchiveAdapterTest.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ZipArchive/ZipArchiveAdapter.php b/src/ZipArchive/ZipArchiveAdapter.php index 58586deea..9d2e29ac1 100644 --- a/src/ZipArchive/ZipArchiveAdapter.php +++ b/src/ZipArchive/ZipArchiveAdapter.php @@ -30,7 +30,6 @@ use function fopen; use function rewind; use function stream_copy_to_stream; -use function stream_with_contents; final class ZipArchiveAdapter implements FilesystemAdapter { @@ -161,7 +160,7 @@ public function deleteDirectory(string $path): void $itemPath = $stats['name']; - if ($prefixedPath === $itemPath || strpos($itemPath, $prefixedPath) !== 0) { + if (strpos($itemPath, $prefixedPath) !== 0) { continue; } diff --git a/src/ZipArchive/ZipArchiveAdapterTest.php b/src/ZipArchive/ZipArchiveAdapterTest.php index 4b64da78f..2dafd550a 100644 --- a/src/ZipArchive/ZipArchiveAdapterTest.php +++ b/src/ZipArchive/ZipArchiveAdapterTest.php @@ -136,7 +136,7 @@ public function deleting_a_directory(): void $this->adapter()->deleteDirectory('one'); $items = iterator_to_array($this->adapter()->listContents('', true)); - $this->assertCount(4, $items); + $this->assertCount(3, $items); } /** From 1535e2116c612205381437b3214d86bedcf0a7e5 Mon Sep 17 00:00:00 2001 From: ToshY <31921460+ToshY@users.noreply.github.com> Date: Wed, 5 Jan 2022 14:05:05 +0100 Subject: [PATCH 42/46] added mup options to config array --- src/AwsS3V3/AwsS3V3Adapter.php | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/AwsS3V3/AwsS3V3Adapter.php b/src/AwsS3V3/AwsS3V3Adapter.php index 157eb09a5..b0309d73c 100644 --- a/src/AwsS3V3/AwsS3V3Adapter.php +++ b/src/AwsS3V3/AwsS3V3Adapter.php @@ -60,6 +60,17 @@ class AwsS3V3Adapter implements FilesystemAdapter 'Tagging', 'WebsiteRedirectLocation', ]; + /** + * @var string[] + */ + public const MUP_AVAILABLE_OPTIONS = [ + 'before_upload', + 'concurrency', + 'mup_threshold', + 'params', + 'part_size', + ]; + /** * @var string[] */ @@ -160,14 +171,14 @@ private function upload(string $path, $body, Config $config): void $key = $this->prefixer->prefixPath($path); $options = $this->createOptionsFromConfig($config); $acl = $options['ACL'] ?? $this->determineAcl($config); - $shouldDetermineMimetype = $body !== '' && ! array_key_exists('ContentType', $options); + $shouldDetermineMimetype = $body !== '' && ! array_key_exists('ContentType', $options['params']); if ($shouldDetermineMimetype && $mimeType = $this->mimeTypeDetector->detectMimeType($key, $body)) { - $options['ContentType'] = $mimeType; + $options['params']['ContentType'] = $mimeType; } try { - $this->client->upload($this->bucket, $key, $body, $acl, ['params' => $options]); + $this->client->upload($this->bucket, $key, $body, $acl, $options); } catch (Throwable $exception) { throw UnableToWriteFile::atLocation($path, '', $exception); } @@ -182,9 +193,9 @@ private function determineAcl(Config $config): string private function createOptionsFromConfig(Config $config): array { - $options = []; + $options = ['params' => []]; - foreach (static::AVAILABLE_OPTIONS as $option) { + foreach (array_merge(static::AVAILABLE_OPTIONS, static::MUP_AVAILABLE_OPTIONS) as $option) { $value = $config->get($option, '__NOT_SET__'); if ($value !== '__NOT_SET__') { From 1435060c761da9777dd6ef59f12d56a65ea90e12 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Sat, 15 Jan 2022 19:39:41 +0100 Subject: [PATCH 43/46] Added test case for ZipArchive --- src/ZipArchive/ZipArchiveAdapterTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ZipArchive/ZipArchiveAdapterTest.php b/src/ZipArchive/ZipArchiveAdapterTest.php index 2dafd550a..7bbd5c550 100644 --- a/src/ZipArchive/ZipArchiveAdapterTest.php +++ b/src/ZipArchive/ZipArchiveAdapterTest.php @@ -17,6 +17,8 @@ use League\Flysystem\UnableToWriteFile; use League\Flysystem\Visibility; +use function iterator_to_array; + /** * @group zip */ @@ -227,6 +229,20 @@ public function failing_to_set_visibility_because_the_file_does_not_exist(): voi $this->adapter()->setVisibility('path.txt', Visibility::PUBLIC); } + /** + * @test + */ + public function deleting_a_directory_with_files_in_it(): void + { + $this->givenWeHaveAnExistingFile('nested/path-a.txt'); + $this->givenWeHaveAnExistingFile('nested/path-b.txt'); + + $this->adapter()->deleteDirectory('nested'); + $listing = iterator_to_array($this->adapter()->listContents('', true)); + + self::assertEquals([], $listing); + } + /** * @test */ From 189e3a32f43e8f7f5772aebd3efc01220f8e64f0 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Sat, 15 Jan 2022 19:49:41 +0100 Subject: [PATCH 44/46] Cleaned up contribution --- src/AwsS3V3/AwsS3V3Adapter.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/AwsS3V3/AwsS3V3Adapter.php b/src/AwsS3V3/AwsS3V3Adapter.php index b0309d73c..f95986012 100644 --- a/src/AwsS3V3/AwsS3V3Adapter.php +++ b/src/AwsS3V3/AwsS3V3Adapter.php @@ -195,7 +195,15 @@ private function createOptionsFromConfig(Config $config): array { $options = ['params' => []]; - foreach (array_merge(static::AVAILABLE_OPTIONS, static::MUP_AVAILABLE_OPTIONS) as $option) { + foreach (static::AVAILABLE_OPTIONS as $option) { + $value = $config->get($option, '__NOT_SET__'); + + if ($value !== '__NOT_SET__') { + $options['params'][$option] = $value; + } + } + + foreach (static::MUP_AVAILABLE_OPTIONS as $option) { $value = $config->get($option, '__NOT_SET__'); if ($value !== '__NOT_SET__') { @@ -427,7 +435,7 @@ public function copy(string $source, string $destination, Config $config): void $this->bucket, $this->prefixer->prefixPath($destination), $this->visibility->visibilityToAcl($visibility), - $this->createOptionsFromConfig($config) + $this->createOptionsFromConfig($config)['params'] ); } catch (Throwable $exception) { throw UnableToCopyFile::fromLocationTo($source, $destination, $exception); From 72451e411636591ee9ba2ff1c6efbcec46eed97a Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Sat, 15 Jan 2022 19:52:01 +0100 Subject: [PATCH 45/46] Prepare changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eca6a16c..0836c3e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 3.0.1 - 2022-01-15 + +### Fixes + +* [ZipArchive] delete top-level directory too when deleting a directory +* [GoogleCloudStorage] Use listing to check for directory existence (consistency) +* [GoogleCloudStorage] Fixed bug where exceptions were not thrown +* [AwsS3V3] Allow passing options for controlling multi-upload options (#1396) +* [Local] Convert windows-style directory separator to unix-style (#1398) + ## 3.0.0 - 2022-01-13 ### Added From adafbfb08efaafcf3a5a0060cfcc830b3f9c7eb9 Mon Sep 17 00:00:00 2001 From: George Zakharov Date: Sat, 15 Jan 2022 19:20:27 +0300 Subject: [PATCH 46/46] Minor style fixes --- .gitignore | 1 + bin/check-versions.php | 9 +++++---- bin/set-flysystem-version.php | 4 ++-- bin/tools.php | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 6fc14647d..faaac7a00 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /.php-cs-fixer.php /.php-cs-fixer.cache /google-cloud-service-account.json +.idea \ No newline at end of file diff --git a/bin/check-versions.php b/bin/check-versions.php index f98bb05ee..095108b26 100644 --- a/bin/check-versions.php +++ b/bin/check-versions.php @@ -8,7 +8,7 @@ * - All required dependencies of the extracted packages MUST be * present in the main composer.json's require(-dev) section. * - Dependency constraints of extracted packages may not exclude - * the constrains of the main package and visa versa. + * the constraints of the main package and visa versa. * - The provided target release argument must be satisfiable by * all of the extracted packages' core dependency constraint. */ @@ -21,7 +21,7 @@ use League\Flysystem\Local\LocalFilesystemAdapter; use League\Flysystem\StorageAttributes; -include_once __DIR__.'/tools.php'; +include_once __DIR__ . '/tools.php'; function constraint_has_conflict(string $mainConstraint, string $packageConstraint): bool @@ -53,7 +53,7 @@ function constraint_has_conflict(string $mainConstraint, string $packageConstrai write_line("🔎 Inspecting composer dependency incompatibilities."); $mainVersion = $argv[1]; -$filesystem = new Filesystem(new LocalFilesystemAdapter(__DIR__.'/../')); +$filesystem = new Filesystem(new LocalFilesystemAdapter(__DIR__ . '/../')); $mainComposer = $filesystem->read('composer.json'); /** @var string[] $otherComposers */ @@ -69,7 +69,7 @@ function constraint_has_conflict(string $mainConstraint, string $packageConstrai $information = json_decode($filesystem->read($composerFile), true); foreach ($information['require'] as $dependency => $constraint) { - if (strpos($dependency, 'ext-') === 0 || $dependency === 'phpseclib/phpseclib') { + if (str_starts_with($dependency, 'ext-') || $dependency === 'phpseclib/phpseclib') { continue; } @@ -79,6 +79,7 @@ function constraint_has_conflict(string $mainConstraint, string $packageConstrai } else { write_line("Composer file {$composerFile} allows league/flysystem:{$mainVersion} with {$constraint}"); } + continue; } diff --git a/bin/set-flysystem-version.php b/bin/set-flysystem-version.php index 7f25df7fc..1036e08b4 100644 --- a/bin/set-flysystem-version.php +++ b/bin/set-flysystem-version.php @@ -5,7 +5,7 @@ use League\Flysystem\Local\LocalFilesystemAdapter; use League\Flysystem\StorageAttributes; -include_once __DIR__.'/tools.php'; +include_once __DIR__ . '/tools.php'; if ( ! isset($argv[1])) { panic('No base version provided'); @@ -15,7 +15,7 @@ write_line("☝️ Setting all flysystem constraints to {$mainVersion}."); -$filesystem = new Filesystem(new LocalFilesystemAdapter(__DIR__.'/../')); +$filesystem = new Filesystem(new LocalFilesystemAdapter(__DIR__ . '/../')); /** @var string[] $otherComposers */ $composerFiles = $filesystem->listContents('src', true) diff --git a/bin/tools.php b/bin/tools.php index add416899..b114a30da 100644 --- a/bin/tools.php +++ b/bin/tools.php @@ -1,6 +1,6 @@