From 8f598a4574a6cf7bd4b59c2a980b120bb045a578 Mon Sep 17 00:00:00 2001 From: Jonathan Hafkamp Date: Thu, 19 Nov 2020 13:14:32 +0100 Subject: [PATCH 1/5] Make Composer v2 ready --- .gitignore | 1 + composer.json | 86 +++++++++++++++++++++++++-------------------------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index db78b71..6e4f756 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ composer.lock vendor phpunit.xml .phpunit-watcher.yml +.idea diff --git a/composer.json b/composer.json index 334efb9..2d7ffd8 100644 --- a/composer.json +++ b/composer.json @@ -1,46 +1,46 @@ { - "name": "kapersoft/sharefile-api", - "description": "A minimal implementation of ShareFile Api", - "keywords": [ - "kapersoft", - "sharefile-api", - "sharefile", - "api", - "php" - ], - "homepage": "https://github.com/kapersoft/sharefile-api", - "license": "MIT", - "authors": [ - { - "name": "Jan Willem Kaper", - "email": "kapersoft@gmail.com", - "homepage": "https://kapersoft.com", - "role": "Developer" - } - ], - "require": { - "php": "^7.0", - "guzzlehttp/guzzle": "^6.2" - }, - "require-dev": { - "larapack/dd": "1.*", - "mikey179/vfsStream": "^1.6", - "phpunit/phpunit": "6.4.x-dev" - }, - "autoload": { - "psr-4": { - "Kapersoft\\Sharefile\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "Kapersoft\\Sharefile\\Test\\": "tests" - } - }, - "scripts": { - "test": "vendor/bin/phpunit" - }, - "config": { - "sort-packages": true + "name": "kapersoft/sharefile-api", + "description": "A minimal implementation of ShareFile Api", + "keywords": [ + "kapersoft", + "sharefile-api", + "sharefile", + "api", + "php" + ], + "homepage": "https://github.com/kapersoft/sharefile-api", + "license": "MIT", + "authors": [ + { + "name": "Jan Willem Kaper", + "email": "kapersoft@gmail.com", + "homepage": "https://kapersoft.com", + "role": "Developer" } + ], + "require": { + "php": "^7.0", + "guzzlehttp/guzzle": "^6.2" + }, + "require-dev": { + "larapack/dd": "1.*", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^6.4" + }, + "autoload": { + "psr-4": { + "Kapersoft\\ShareFile\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Kapersoft\\ShareFile\\Test\\": "tests" + } + }, + "scripts": { + "test": "vendor/bin/phpunit" + }, + "config": { + "sort-packages": true + } } From b4c700b75c124157be5725e3ffdefd7850b35d08 Mon Sep 17 00:00:00 2001 From: Jonathan Hafkamp Date: Thu, 19 Nov 2020 13:17:53 +0100 Subject: [PATCH 2/5] Change namespace to be compatible with Composer v2. Could be a breaking change. --- composer.json | 4 +- src/Client.php | 280 ++++++++++++++++++++++++++++--------------------- 2 files changed, 160 insertions(+), 124 deletions(-) diff --git a/composer.json b/composer.json index 2d7ffd8..5e3b72d 100644 --- a/composer.json +++ b/composer.json @@ -29,12 +29,12 @@ }, "autoload": { "psr-4": { - "Kapersoft\\ShareFile\\": "src" + "Kapersoft\\Sharefile\\": "src" } }, "autoload-dev": { "psr-4": { - "Kapersoft\\ShareFile\\Test\\": "tests" + "Kapersoft\\Sharefile\\Test\\": "tests" } }, "scripts": { diff --git a/src/Client.php b/src/Client.php index 1ce9188..b680310 100644 --- a/src/Client.php +++ b/src/Client.php @@ -1,13 +1,13 @@ authenticate($hostname, $client_id, $client_secret, $username, $password, $handler); - if (! isset($response['access_token']) || ! isset($response['subdomain'])) { + if (!isset($response['access_token']) || !isset($response['subdomain'])) { throw new Exception("Incorrect response from Authentication: 'access_token' or 'subdomain' is missing."); } @@ -87,19 +93,25 @@ public function __construct(string $hostname, string $client_id, string $client_ /** * ShareFile authentication using username/password. * - * @param string $hostname ShareFile hostname - * @param string $client_id OAuth2 client_id - * @param string $client_secret OAuth2 client_secret - * @param string $username ShareFile username - * @param string $password ShareFile password - * @param MockHandler|HandlerStack $handler Guzzle Handler + * @param string $hostname ShareFile hostname + * @param string $client_id OAuth2 client_id + * @param string $client_secret OAuth2 client_secret + * @param string $username ShareFile username + * @param string $password ShareFile password + * @param MockHandler|HandlerStack $handler Guzzle Handler * + * @return array * @throws Exception * - * @return array */ - protected function authenticate(string $hostname, string $client_id, string $client_secret, string $username, string $password, $handler = null):array - { + protected function authenticate( + string $hostname, + string $client_id, + string $client_secret, + string $username, + string $password, + $handler = null + ): array { $uri = "https://{$hostname}/oauth/token"; $parameters = [ @@ -130,11 +142,11 @@ protected function authenticate(string $hostname, string $client_id, string $cli /** * Get user details. * - * @param string $userId ShareFile user id (optional) + * @param string $userId ShareFile user id (optional) * * @return array */ - public function getUser(string $userId = ''):array + public function getUser(string $userId = ''): array { return $this->get("Users({$userId})"); } @@ -142,15 +154,19 @@ public function getUser(string $userId = ''):array /** * Create a folder. * - * @param string $parentId Id of the parent folder - * @param string $name Name - * @param string $description Description - * @param bool $overwrite Overwrite folder + * @param string $parentId Id of the parent folder + * @param string $name Name + * @param string $description Description + * @param bool $overwrite Overwrite folder * * @return array */ - public function createFolder(string $parentId, string $name, string $description = '', bool $overwrite = false):array - { + public function createFolder( + string $parentId, + string $name, + string $description = '', + bool $overwrite = false + ): array { $parameters = $this->buildHttpQuery( [ 'overwrite' => $overwrite, @@ -169,12 +185,12 @@ public function createFolder(string $parentId, string $name, string $description /** * Get Folder/File using Id. * - * @param string $itemId Item id - * @param bool $getChildren Include children + * @param string $itemId Item id + * @param bool $getChildren Include children * * @return array */ - public function getItemById(string $itemId, bool $getChildren = false):array + public function getItemById(string $itemId, bool $getChildren = false): array { $parameters = $getChildren === true ? '$expand=Children' : ''; @@ -184,12 +200,12 @@ public function getItemById(string $itemId, bool $getChildren = false):array /** * Get Folder/File using path. * - * @param string $path Path - * @param string $itemId Id of the root folder (optional) + * @param string $path Path + * @param string $itemId Id of the root folder (optional) * * @return array */ - public function getItemByPath(string $path, string $itemId = ''):array + public function getItemByPath(string $path, string $itemId = ''): array { if (empty($itemId)) { return $this->get("Items/ByPath?Path={$path}"); @@ -201,11 +217,11 @@ public function getItemByPath(string $path, string $itemId = ''):array /** * Get breadcrumps of an item. * - * @param string $itemId Item Id + * @param string $itemId Item Id * * @return array */ - public function getItemBreadcrumps(string $itemId):array + public function getItemBreadcrumps(string $itemId): array { return $this->get("Items({$itemId})/Breadcrumbs"); } @@ -213,13 +229,13 @@ public function getItemBreadcrumps(string $itemId):array /** * Copy an item. * - * @param string $targetId Id of the target folder - * @param string $itemId Id of the copied item - * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) + * @param string $targetId Id of the target folder + * @param string $itemId Id of the copied item + * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) * * @return array */ - public function copyItem(string $targetId, string $itemId, bool $overwrite = false):array + public function copyItem(string $targetId, string $itemId, bool $overwrite = false): array { $parameters = $this->buildHttpQuery( [ @@ -234,14 +250,14 @@ public function copyItem(string $targetId, string $itemId, bool $overwrite = fal /** * Update an item. * - * @param string $itemId Id of the item - * @param array $data New data - * @param bool $forceSync Indicates whether operation is to be executed synchronously (optional) - * @param bool $notify Indicates whether an email should be sent to users subscribed to Upload Notifications (optional) + * @param string $itemId Id of the item + * @param array $data New data + * @param bool $forceSync Indicates whether operation is to be executed synchronously (optional) + * @param bool $notify Indicates whether an email should be sent to users subscribed to Upload Notifications (optional) * * @return array */ - public function updateItem(string $itemId, array $data, bool $forceSync = true, bool $notify = true):array + public function updateItem(string $itemId, array $data, bool $forceSync = true, bool $notify = true): array { $parameters = $this->buildHttpQuery( [ @@ -256,13 +272,13 @@ public function updateItem(string $itemId, array $data, bool $forceSync = true, /** * Delete an item. * - * @param string $itemId Item id - * @param bool $singleversion True it will delete only the specified version rather than all sibling files with the same filename (optional) - * @param bool $forceSync True will block the operation from taking place asynchronously (optional) + * @param string $itemId Item id + * @param bool $singleversion True it will delete only the specified version rather than all sibling files with the same filename (optional) + * @param bool $forceSync True will block the operation from taking place asynchronously (optional) * * @return string */ - public function deleteItem(string $itemId, bool $singleversion = false, bool $forceSync = false):string + public function deleteItem(string $itemId, bool $singleversion = false, bool $forceSync = false): string { $parameters = $this->buildHttpQuery( [ @@ -277,12 +293,12 @@ public function deleteItem(string $itemId, bool $singleversion = false, bool $fo /** * Get temporary download URL for an item. * - * @param string $itemId Item id - * @param bool $includeallversions For folder downloads only, includes old versions of files in the folder in the zip when true, current versions only when false (default) + * @param string $itemId Item id + * @param bool $includeallversions For folder downloads only, includes old versions of files in the folder in the zip when true, current versions only when false (default) * * @return array */ - public function getItemDownloadUrl(string $itemId, bool $includeallversions = false):array + public function getItemDownloadUrl(string $itemId, bool $includeallversions = false): array { $parameters = $this->buildHttpQuery( [ @@ -297,8 +313,8 @@ public function getItemDownloadUrl(string $itemId, bool $includeallversions = fa /** * Get contents of and item. * - * @param string $itemId Item id - * @param bool $includeallversions $includeallversions For folder downloads only, includes old versions of files in the folder in the zip when true, current versions only when false (default) + * @param string $itemId Item id + * @param bool $includeallversions $includeallversions For folder downloads only, includes old versions of files in the folder in the zip when true, current versions only when false (default) * * @return mixed */ @@ -317,19 +333,27 @@ public function getItemContents(string $itemId, bool $includeallversions = false /** * Get the Chunk Uri to start a file-upload. * - * @param string $method Upload method (Standard or Streamed) - * @param string $filename Name of file - * @param string $folderId Id of the parent folder - * @param bool $unzip Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional) - * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) - * @param bool $notify Indicates whether users will be notified of this upload - based on folder preferences (optional) - * @param bool $raw Send contents contents directly in the POST body (=true) or send contents in MIME format (=false) (optional) - * @param resource $stream Resource stream of the contents (optional) + * @param string $method Upload method (Standard or Streamed) + * @param string $filename Name of file + * @param string $folderId Id of the parent folder + * @param bool $unzip Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional) + * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) + * @param bool $notify Indicates whether users will be notified of this upload - based on folder preferences (optional) + * @param bool $raw Send contents contents directly in the POST body (=true) or send contents in MIME format (=false) (optional) + * @param resource $stream Resource stream of the contents (optional) * * @return array */ - public function getChunkUri(string $method, string $filename, string $folderId, bool $unzip = false, $overwrite = true, bool $notify = true, bool $raw = false, $stream = null):array - { + public function getChunkUri( + string $method, + string $filename, + string $folderId, + bool $unzip = false, + $overwrite = true, + bool $notify = true, + bool $raw = false, + $stream = null + ): array { $parameters = $this->buildHttpQuery( [ 'method' => $method, @@ -356,16 +380,21 @@ public function getChunkUri(string $method, string $filename, string $folderId, /** * Upload a file using a single HTTP POST. * - * @param string $filename Name of file - * @param string $folderId Id of the parent folder - * @param bool $unzip Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional) - * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) - * @param bool $notify Indicates whether users will be notified of this upload - based on folder preferences (optional) + * @param string $filename Name of file + * @param string $folderId Id of the parent folder + * @param bool $unzip Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional) + * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) + * @param bool $notify Indicates whether users will be notified of this upload - based on folder preferences (optional) * * @return string */ - public function uploadFileStandard(string $filename, string $folderId, bool $unzip = false, bool $overwrite = true, bool $notify = true):string - { + public function uploadFileStandard( + string $filename, + string $folderId, + bool $unzip = false, + bool $overwrite = true, + bool $notify = true + ): string { $chunkUri = $this->getChunkUri('standard', $filename, $folderId, $unzip, $overwrite, $notify); $response = $this->client->request( @@ -387,18 +416,25 @@ public function uploadFileStandard(string $filename, string $folderId, bool $unz /** * Upload a file using multiple HTTP POSTs. * - * @param mixed $stream Stream resource - * @param string $folderId Id of the parent folder - * @param string $filename Filename (optional) - * @param bool $unzip Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional) - * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) - * @param bool $notify Indicates whether users will be notified of this upload - based on folder preferences (optional) - * @param int $chunkSize Maximum size of the individual HTTP posts in bytes + * @param mixed $stream Stream resource + * @param string $folderId Id of the parent folder + * @param string $filename Filename (optional) + * @param bool $unzip Indicates that the upload is a Zip file, and contents must be extracted at the end of upload. The resulting files and directories will be placed in the target folder. If set to false, the ZIP file is uploaded as a single file. Default is false (optional) + * @param bool $overwrite Indicates whether items with the same name will be overwritten or not (optional) + * @param bool $notify Indicates whether users will be notified of this upload - based on folder preferences (optional) + * @param int $chunkSize Maximum size of the individual HTTP posts in bytes * * @return string */ - public function uploadFileStreamed($stream, string $folderId, string $filename = null, bool $unzip = false, bool $overwrite = true, bool $notify = true, int $chunkSize = null):string - { + public function uploadFileStreamed( + $stream, + string $folderId, + string $filename = null, + bool $unzip = false, + bool $overwrite = true, + bool $notify = true, + int $chunkSize = null + ): string { $filename = $filename ?? stream_get_meta_data($stream)['uri']; if (empty($filename)) { return 'Error: no filename'; @@ -410,7 +446,7 @@ public function uploadFileStreamed($stream, string $folderId, string $filename = // First Chunk $data = $this->readChunk($stream, $chunkSize); - while (! ((strlen($data) < $chunkSize) || feof($stream))) { + while (!((strlen($data) < $chunkSize) || feof($stream))) { $parameters = $this->buildHttpQuery( [ 'index' => $index, @@ -437,7 +473,7 @@ public function uploadFileStreamed($stream, string $folderId, string $filename = 'byteOffset' => $index * $chunkSize, 'hash' => md5($data), 'filehash' => Psr7\hash(Psr7\stream_for($stream), 'md5'), - 'finish' => true, + 'finish' => true, ] ); @@ -447,12 +483,12 @@ public function uploadFileStreamed($stream, string $folderId, string $filename = /** * Get Thumbnail of an item. * - * @param string $itemId Item id - * @param int $size Thumbnail size: THUMBNAIL_SIZE_M or THUMBNAIL_SIZE_L (optional) + * @param string $itemId Item id + * @param int $size Thumbnail size: THUMBNAIL_SIZE_M or THUMBNAIL_SIZE_L (optional) * * @return array */ - public function getThumbnailUrl(string $itemId, int $size = 75):array + public function getThumbnailUrl(string $itemId, int $size = 75): array { $parameters = $this->buildHttpQuery( [ @@ -467,11 +503,11 @@ public function getThumbnailUrl(string $itemId, int $size = 75):array /** * Get browser link for an item. * - * @param string $itemId Item id + * @param string $itemId Item id * * @return array */ - public function getWebAppLink(string $itemId):array + public function getWebAppLink(string $itemId): array { return $this->post("Items({$itemId})/WebAppLink"); } @@ -479,12 +515,12 @@ public function getWebAppLink(string $itemId):array /** * Share Share for external user. * - * @param array $options Share options - * @param bool $notify Indicates whether user will be notified if item is downloaded (optional) + * @param array $options Share options + * @param bool $notify Indicates whether user will be notified if item is downloaded (optional) * * @return array */ - public function createShare(array $options, $notify = false):array + public function createShare(array $options, $notify = false): array { $parameters = $this->buildHttpQuery( [ @@ -499,14 +535,14 @@ public function createShare(array $options, $notify = false):array /** * Get AccessControl List for an item. * - * @param string $itemId Id of an item - * @param string $userId Id of an user + * @param string $itemId Id of an item + * @param string $userId Id of an user * * @return array */ - public function getItemAccessControls(string $itemId, string $userId = ''):array + public function getItemAccessControls(string $itemId, string $userId = ''): array { - if (! empty($userId)) { + if (!empty($userId)) { return $this->get("AccessControls(principalid={$userId},itemid={$itemId})"); } else { return $this->get("Items({$itemId})/AccessControls"); @@ -516,25 +552,25 @@ public function getItemAccessControls(string $itemId, string $userId = ''):array /** * Build API uri. * - * @param string $endpoint API endpoint + * @param string $endpoint API endpoint * * @return string */ protected function buildUri(string $endpoint): string { - return "https://{$this->token['subdomain']}.sf-api.com/sf/v3/{$endpoint}"; + return "https://{$this->token['subdomain']}.sf-api.com/sf/v3/{$endpoint}"; } /** * Make a request to the API. * - * @param string $method HTTP Method - * @param string $endpoint API endpoint - * @param mixed|string|array $json POST body (optional) + * @param string $method HTTP Method + * @param string $endpoint API endpoint + * @param mixed|string|array $json POST body (optional) * + * @return mixed * @throws Exception * - * @return mixed */ protected function request(string $method, string $endpoint, $json = null) { @@ -555,7 +591,7 @@ protected function request(string $method, string $endpoint, $json = null) /** * Shorthand for GET-request. * - * @param string $endpoint API endpoint + * @param string $endpoint API endpoint * * @return mixed */ @@ -567,8 +603,8 @@ protected function get(string $endpoint) /** * Shorthand for POST-request. * - * @param string $endpoint API endpoint - * @param mixed|string|array $json POST body (optional) + * @param string $endpoint API endpoint + * @param mixed|string|array $json POST body (optional) * * @return mixed */ @@ -580,8 +616,8 @@ protected function post(string $endpoint, $json = null) /** * Shorthand for PATCH-request. * - * @param string $endpoint API endpoint - * @param mixed|string|array $json POST body (optional) + * @param string $endpoint API endpoint + * @param mixed|string|array $json POST body (optional) * * @return mixed */ @@ -593,7 +629,7 @@ protected function patch(string $endpoint, $json = null) /** * Shorthand for DELETE-request. * - * @param string $endpoint API endpoint + * @param string $endpoint API endpoint * * @return string|array */ @@ -605,8 +641,8 @@ protected function delete(string $endpoint) /** * Upload a chunk of data using HTTP POST body. * - * @param string $uri Upload URI - * @param string $data Contents to upload + * @param string $uri Upload URI + * @param string $data Contents to upload * * @return string|array */ @@ -620,7 +656,7 @@ protected function uploadChunk($uri, $data) 'Content-Length' => strlen($data), 'Content-Type' => 'application/octet-stream', ], - 'body' => $data, + 'body' => $data, ] ); @@ -632,16 +668,16 @@ protected function uploadChunk($uri, $data) * from network streams). This function repeatedly calls fread until the requested number of * bytes have been read or we've reached EOF. * - * @param resource $stream - * @param int $chunkSize + * @param resource $stream + * @param int $chunkSize * - * @throws Exception * @return string + * @throws Exception */ protected function readChunk($stream, int $chunkSize) { $chunk = ''; - while (! feof($stream) && $chunkSize > 0) { + while (!feof($stream) && $chunkSize > 0) { $part = fread($stream, $chunkSize); if ($part === false) { throw new Exception('Error reading from $stream.'); @@ -656,7 +692,7 @@ protected function readChunk($stream, int $chunkSize) /** * Handle ClientException. * - * @param ClientException $exception ClientException + * @param ClientException $exception ClientException * * @return Exception */ @@ -672,16 +708,16 @@ protected function determineException(ClientException $exception): Exception /** * Build HTTP query. * - * @param array $parameters Query parameters + * @param array $parameters Query parameters * * @return string */ - protected function buildHttpQuery(array $parameters):string + protected function buildHttpQuery(array $parameters): string { return http_build_query( array_map( function ($parameter) { - if (! is_bool($parameter)) { + if (!is_bool($parameter)) { return $parameter; } @@ -695,13 +731,13 @@ function ($parameter) { /** * Validate JSON. * - * @param mixed $data JSON variable + * @param mixed $data JSON variable * * @return bool */ - protected function jsonValidator($data = null):bool + protected function jsonValidator($data = null): bool { - if (! empty($data)) { + if (!empty($data)) { @json_decode($data); return json_last_error() === JSON_ERROR_NONE; From c2df9d796334ec6bfa34af28417a2c09524949a3 Mon Sep 17 00:00:00 2001 From: Jonathan Hafkamp Date: Tue, 23 Feb 2021 15:34:55 +0100 Subject: [PATCH 3/5] Change project name --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5e3b72d..11a9576 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "kapersoft/sharefile-api", + "name": "creativeorange/sharefile-api", "description": "A minimal implementation of ShareFile Api", "keywords": [ "kapersoft", From 91517475aa3013913118fc32971ec30f2c403639 Mon Sep 17 00:00:00 2001 From: Jonathan Hafkamp Date: Tue, 23 Feb 2021 16:14:24 +0100 Subject: [PATCH 4/5] Bump Guzzle to 7 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 11a9576..3b92682 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": "^7.0", - "guzzlehttp/guzzle": "^6.2" + "guzzlehttp/guzzle": "^7.0.1" }, "require-dev": { "larapack/dd": "1.*", From e8eba16d85418cc2a5bee00e712dd3186c141c59 Mon Sep 17 00:00:00 2001 From: Jonathan Hafkamp Date: Tue, 12 Oct 2021 14:58:24 +0200 Subject: [PATCH 5/5] Prepare upgrade to PHP8 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3b92682..b80eb8f 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ } ], "require": { - "php": "^7.0", + "php": "^7.0|^8.0", "guzzlehttp/guzzle": "^7.0.1" }, "require-dev": {