From 147744d32f866e233f3535683f1e00f1b473c83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mar=C3=ADn?= Date: Thu, 8 May 2025 10:54:03 +0200 Subject: [PATCH 1/4] fix: php 8.4 deprecation notices chore: replace symfony/http-client with guzzlehttp/guzzle chore: add phpstan support and fix phpstan issues --- composer.json | 2 +- lib/Auth/AuthorizationToken.php | 2 -- lib/Auth/AuthorizationTokenInterface.php | 2 +- lib/Http/Client.php | 37 ++++++++++-------------- lib/Model/Update.php | 6 ++-- lib/Service/UpdateService.php | 18 ++---------- phpstan.neon | 11 +++++++ 7 files changed, 34 insertions(+), 44 deletions(-) create mode 100644 phpstan.neon diff --git a/composer.json b/composer.json index 801e35c..cc20af9 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "symfony/http-client": "^4.3" + "guzzlehttp/guzzle": "^7" }, "require-dev": { "squizlabs/php_codesniffer": "3.*", diff --git a/lib/Auth/AuthorizationToken.php b/lib/Auth/AuthorizationToken.php index 35636c3..f4576b7 100644 --- a/lib/Auth/AuthorizationToken.php +++ b/lib/Auth/AuthorizationToken.php @@ -19,8 +19,6 @@ public function __construct(string $accessToken) /** * Get Access Token. - * - * @return string */ public function getAccessToken(): string { diff --git a/lib/Auth/AuthorizationTokenInterface.php b/lib/Auth/AuthorizationTokenInterface.php index 96b5435..f205135 100644 --- a/lib/Auth/AuthorizationTokenInterface.php +++ b/lib/Auth/AuthorizationTokenInterface.php @@ -4,5 +4,5 @@ interface AuthorizationTokenInterface { - public function getAccessToken(); + public function getAccessToken() : string; } diff --git a/lib/Http/Client.php b/lib/Http/Client.php index 08c0371..3812ef1 100644 --- a/lib/Http/Client.php +++ b/lib/Http/Client.php @@ -3,20 +3,13 @@ namespace BufferSDK\Http; use BufferSDK\Auth\AuthorizationTokenInterface; -use Symfony\Component\HttpClient\HttpClient; -use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; -use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; -use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; -use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; -use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; -use Symfony\Contracts\HttpClient\HttpClientInterface; class Client implements ClientInterface { /** @var string */ protected $baseURL = 'https://api.bufferapp.com/1/'; - /** @var HttpClientInterface */ + /** @var \GuzzleHttp\Client */ private $httpClient; /** @@ -24,32 +17,34 @@ class Client implements ClientInterface */ public function __construct(AuthorizationTokenInterface $auth) { - $this->httpClient = HttpClient::create(['headers' => [ - 'Authorization' => 'Bearer '.$auth->getAccessToken(), - ]]); + $this->httpClient = new \GuzzleHttp\Client([ + 'base_uri' => $this->baseURL, + \GuzzleHttp\RequestOptions::HEADERS => [ + 'Authorization' => 'Bearer ' . $auth->getAccessToken(), + ], + ]); } /** * Create Http Request and send the request. * * @param string $method - * @param string $url + * @param string $endpoint * @param array $options * * @return array * - * @throws TransportExceptionInterface - * @throws DecodingExceptionInterface When the body cannot be decoded to an array - * @throws TransportExceptionInterface When a network error occurs - * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached - * @throws ClientExceptionInterface On a 4xx when $throw is true - * @throws ServerExceptionInterface On a 5xx when $throw is true + * @throws \GuzzleHttp\Exception\GuzzleException */ public function createHttpRequest(string $method, string $endpoint, array $options = []): array { - $response = $this->httpClient->request($method, $this->baseURL.$endpoint, $options); - $responseArray = $response->toArray(); + $response = $this->httpClient->request($method, $endpoint, $options); + $responseBody = json_decode($response->getBody()->getContents(), true, 512, JSON_BIGINT_AS_STRING); - return $responseArray; + if (\JSON_ERROR_NONE !== json_last_error()) { + throw new \Exception(json_last_error_msg() . sprintf(' for "%s".', $method), json_last_error()); + } + + return $responseBody; } } diff --git a/lib/Model/Update.php b/lib/Model/Update.php index 2aab8df..208eab5 100644 --- a/lib/Model/Update.php +++ b/lib/Model/Update.php @@ -18,7 +18,7 @@ class Update /** * The status update text. * - * @var string + * @var string|null */ public $text = null; @@ -67,7 +67,7 @@ class Update * now parameter. When using ISO 8601 format, if no UTC offset is specified, * UTC is assumed. * - * @var \DateTime + * @var string|null */ public $scheduled_at = null; @@ -125,7 +125,7 @@ public function schedule($when): self return $this; } - public function validate() + public function validate(): void { if ((!isset($this->text) || trim($this->text) === '')) { throw new InvalidArgumentException('text field should be filled'); diff --git a/lib/Service/UpdateService.php b/lib/Service/UpdateService.php index aa11034..186dd7c 100644 --- a/lib/Service/UpdateService.php +++ b/lib/Service/UpdateService.php @@ -36,13 +36,6 @@ public function getUpdates(string $profileID): array * Returns an array of updates that are currently in the buffer for an * individual social media profile. * - * @param string $profileID - * - * @param int $page - * @param int $count - * @param string|null $since - * @param bool $utc - * * @return array */ public function getPendingUpdates(string $profileID, int $page = 1, int $count = 100, string $since = "", bool $utc = false): array @@ -54,13 +47,6 @@ public function getPendingUpdates(string $profileID, int $page = 1, int $count = * Returns an array of updates that have been sent from the buffer for an * individual social media profile. * - * @param string $profileID - * - * @param int $page - * @param int $count - * @param string|null $since - * @param bool $utc - * * @return array */ public function getSentUpdates(string $profileID, int $page = 1, int $count = 100, string $since = "", bool $utc = false, string $filter = ""): array @@ -77,7 +63,7 @@ public function getSentUpdates(string $profileID, int $page = 1, int $count = 10 * * @return array */ - public function reorderUpdates(string $profileID, array $order, int $offset = null, bool $utc = false): array + public function reorderUpdates(string $profileID, array $order, ?int $offset = null, bool $utc = false): array { return $this->client->createHttpRequest( 'POST', @@ -102,7 +88,7 @@ public function reorderUpdates(string $profileID, array $order, int $offset = nu * * @return array */ - public function shuffleUpdates(string $profileID, int $count = null, bool $utc = false): array + public function shuffleUpdates(string $profileID, ?int $count = null, bool $utc = false): array { return $this->client->createHttpRequest( 'POST', diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..183067d --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,11 @@ +parameters: + level: 6 + paths: + - ./ + excludePaths: + - vendor/* + - tests/* + ignoreErrors: + - + identifier: missingType.iterableValue + From 51ebf728b280430bb5af00679329a3ad9b6b8bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mar=C3=ADn?= Date: Thu, 8 May 2025 11:02:55 +0200 Subject: [PATCH 2/4] fix: guzzle implementation refactor: remove old-syntax arrays --- lib/Model/Schedule.php | 28 +++++++++++++------------- lib/Model/Update.php | 8 ++++---- lib/Service/ProfileService.php | 16 +++++++-------- lib/Service/UpdateService.php | 36 +++++++++++++++++----------------- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/lib/Model/Schedule.php b/lib/Model/Schedule.php index e120bab..b0aaa8c 100644 --- a/lib/Model/Schedule.php +++ b/lib/Model/Schedule.php @@ -19,29 +19,29 @@ class Schedule * * @var array */ - protected $days = array(); + protected $days = []; /** * Array of scheduled times. * * @var array */ - protected $times = array(); + protected $times = []; /** * Create a new Schedule instance. * - * @param array $days - * @param array $times + * @param array $days + * @param array $times * @return void */ - public function __construct($days = array(), $times = array()) + public function __construct($days = [], $times = []) { - if (! empty($days)) { + if (!empty($days)) { $this->addDay($days); } - if (! empty($times)) { + if (!empty($times)) { $this->addTime($times); } } @@ -49,17 +49,17 @@ public function __construct($days = array(), $times = array()) /** * Schedule a new day. * - * @param string|array $day + * @param string|array $day * @return Schedule */ public function addDay($day): self { - $available = array('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'); + $available = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']; - foreach ((array) $day as $value) { + foreach ((array)$day as $value) { // only accept valid values - if (! in_array($value, $available)) { - throw new InvalidArgumentException('Day must be a valid value: '.implode(', ', $available)); + if (!in_array($value, $available)) { + throw new InvalidArgumentException('Day must be a valid value: ' . implode(', ', $available)); } $this->days[] = $value; @@ -76,9 +76,9 @@ public function addDay($day): self */ public function addTime($time): self { - foreach ((array) $time as $value) { + foreach ((array)$time as $value) { // only accept valid times (HH:mm) - if (! preg_match('#([01][0-9]|2[0-3]):[0-5][0-9]#', $value)) { + if (!preg_match('#([01][0-9]|2[0-3]):[0-5][0-9]#', $value)) { throw new InvalidArgumentException('Time must be valid: HH:mm.'); } diff --git a/lib/Model/Update.php b/lib/Model/Update.php index 208eab5..57a85c0 100644 --- a/lib/Model/Update.php +++ b/lib/Model/Update.php @@ -28,7 +28,7 @@ class Update * * @var array */ - public $profiles = array(); + public $profiles = []; /** * If shorten is false links within the text will not be automatically @@ -60,7 +60,7 @@ class Update * * @var array */ - public $media = array(); + public $media = []; /** * A date describing when the update should be posted. Overrides any top or @@ -93,11 +93,11 @@ public function addProfile($id): self */ public function addMedia($key, $value): self { - $available = array('link', 'description', 'picture', 'photo'); + $available = ['link', 'description', 'picture', 'photo']; // accept only valid types for media if (!in_array($key, $available)) { - throw new InvalidArgumentException('Media type must be a valid value: '.implode(', ', $available).'.'); + throw new InvalidArgumentException('Media type must be a valid value: ' . implode(', ', $available) . '.'); } $this->media[$key] = $value; diff --git a/lib/Service/ProfileService.php b/lib/Service/ProfileService.php index f0226b8..1f1b78a 100644 --- a/lib/Service/ProfileService.php +++ b/lib/Service/ProfileService.php @@ -39,7 +39,7 @@ public function getProfiles(): array */ public function getProfile(string $profileID): array { - return $this->client->createHttpRequest('GET', 'profiles/'.$profileID.'.json'); + return $this->client->createHttpRequest('GET', "profiles/{$profileID}.json"); } /** @@ -51,7 +51,7 @@ public function getProfile(string $profileID): array */ public function getSchedules(string $profileID): array { - return $this->client->createHttpRequest('GET', 'profiles/'.$profileID.'/schedules.json'); + return $this->client->createHttpRequest('GET', "profiles/{$profileID}/schedules.json"); } /** @@ -64,17 +64,17 @@ public function getSchedules(string $profileID): array */ public function updateSchedule(string $profileID, Schedule $schedule): array { - $payload = array('schedules' => array()); - $payload['schedules'][] = array( - 'days' => $schedule->getDays(), + $payload = ['schedules' => []]; + $payload['schedules'][] = [ + 'days' => $schedule->getDays(), 'times' => $schedule->getTimes(), - ); + ]; return $this->client->createHttpRequest( 'POST', - 'profiles/'.$profileID.'/schedules/update.json', + "profiles/{$profileID}/schedules/update.json", [ - 'body' => $payload, + \GuzzleHttp\RequestOptions::JSON => $payload, ] ); } diff --git a/lib/Service/UpdateService.php b/lib/Service/UpdateService.php index 186dd7c..6bb6f92 100644 --- a/lib/Service/UpdateService.php +++ b/lib/Service/UpdateService.php @@ -29,7 +29,7 @@ public function __construct(ClientInterface $client) */ public function getUpdates(string $profileID): array { - return $this->client->createHttpRequest('GET', 'updates/'.$profileID.'.json'); + return $this->client->createHttpRequest('GET', "updates/{$profileID}.json"); } /** @@ -40,7 +40,7 @@ public function getUpdates(string $profileID): array */ public function getPendingUpdates(string $profileID, int $page = 1, int $count = 100, string $since = "", bool $utc = false): array { - return $this->client->createHttpRequest('GET', 'profiles/'.$profileID.'/updates/pending.json?page='.$page.'&count='.$count.'&since='.$since.'&utc='.$utc); + return $this->client->createHttpRequest('GET', "profiles/{$profileID}/updates/pending.json?page={$page}&count={$count}&since={$since}&utc={$utc}"); } /** @@ -51,7 +51,7 @@ public function getPendingUpdates(string $profileID, int $page = 1, int $count = */ public function getSentUpdates(string $profileID, int $page = 1, int $count = 100, string $since = "", bool $utc = false, string $filter = ""): array { - return $this->client->createHttpRequest('GET', 'profiles/'.$profileID.'/updates/sent.json?page='.$page.'&count='.$count.'&since='.$since.'&utc='.$utc.'&filter='.$filter); + return $this->client->createHttpRequest('GET', "profiles/{$profileID}/updates/sent.json?page={$page}&count={$count}&since={$since}&utc={$utc}&filter={$filter}"); } /** @@ -67,9 +67,9 @@ public function reorderUpdates(string $profileID, array $order, ?int $offset = n { return $this->client->createHttpRequest( 'POST', - 'profiles/'.$profileID.'/updates/reorder.json', + "profiles/{$profileID}/updates/reorder.json", [ - 'body' => [ + \GuzzleHttp\RequestOptions::JSON => [ 'order' => $order, 'offset' => $offset, 'utc' => $utc, @@ -92,9 +92,9 @@ public function shuffleUpdates(string $profileID, ?int $count = null, bool $utc { return $this->client->createHttpRequest( 'POST', - 'profiles/'.$profileID.'/updates/shuffle.json', + "profiles/{$profileID}/updates/shuffle.json", [ - 'body' => [ + \GuzzleHttp\RequestOptions::JSON => [ 'count' => $count, 'utc' => $utc, ], @@ -111,13 +111,13 @@ public function createUpdate(Update $update): array { $update->validate(); - $payload = array( + $payload = [ 'text' => $update->text, 'profile_ids' => $update->profiles, 'shorten' => $update->shorten, 'now' => $update->now, 'top' => $update->top, - ); + ]; if (!empty($update->media)) { $payload['media'] = $update->media; @@ -130,7 +130,7 @@ public function createUpdate(Update $update): array 'POST', 'updates/create.json', [ - 'body' => $payload, + \GuzzleHttp\RequestOptions::JSON => $payload, ] ); } @@ -144,10 +144,10 @@ public function updateUpdate(Update $update): array { $update->validate(); - $payload = array( + $payload = [ 'text' => $update->text, 'now' => $update->now, - ); + ]; if (!empty($update->media)) { $payload['media'] = $update->media; @@ -158,9 +158,9 @@ public function updateUpdate(Update $update): array return $this->client->createHttpRequest( 'POST', - 'updates/'.$update->id.'/update.json', + "updates/{$update->id}/update.json", [ - 'body' => $payload, + \GuzzleHttp\RequestOptions::JSON => $payload, ] ); } @@ -175,7 +175,7 @@ public function shareUpdate(string $updateID): array { return $this->client->createHttpRequest( 'POST', - 'updates/'.$updateID.'/share.json' + "updates/{$updateID}/share.json" ); } @@ -188,7 +188,7 @@ public function deleteUpdate(string $updateID): array { return $this->client->createHttpRequest( 'POST', - 'updates/'.$updateID.'/destroy.json' + "updates/{$updateID}/destroy.json" ); } @@ -204,7 +204,7 @@ public function moveToTopUpdate(string $updateID): array { return $this->client->createHttpRequest( 'POST', - 'updates/'.$updateID.'/move_to_top.json' + "updates/{$updateID}/move_to_top.json" ); } @@ -217,6 +217,6 @@ public function moveToTopUpdate(string $updateID): array */ public function getSharesLink(string $url): array { - return $this->client->createHttpRequest('GET', 'links/shares.json?url='.$url); + return $this->client->createHttpRequest('GET', "links/shares.json?url={$url}"); } } From 40b0eaa5068e3284d1da2e9ac6f12c11ca19cd3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mar=C3=ADn?= Date: Thu, 8 May 2025 12:02:21 +0200 Subject: [PATCH 3/4] fix: add missing curl handler for guzzle http --- lib/Http/Client.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Http/Client.php b/lib/Http/Client.php index 3812ef1..3d19676 100644 --- a/lib/Http/Client.php +++ b/lib/Http/Client.php @@ -19,6 +19,7 @@ public function __construct(AuthorizationTokenInterface $auth) { $this->httpClient = new \GuzzleHttp\Client([ 'base_uri' => $this->baseURL, + 'handler' => new \GuzzleHttp\Handler\CurlHandler(), \GuzzleHttp\RequestOptions::HEADERS => [ 'Authorization' => 'Bearer ' . $auth->getAccessToken(), ], From c4d1939d02dde7ef6c2c1898a6222ddf2b538729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mar=C3=ADn?= Date: Thu, 8 May 2025 15:17:05 +0200 Subject: [PATCH 4/4] chore: update symfony/http-client --- README.md | 2 +- composer.json | 7 +++--- lib/Http/Client.php | 40 ++++++++++++++++------------------ lib/Service/ProfileService.php | 2 +- lib/Service/UpdateService.php | 8 +++---- 5 files changed, 29 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index a5e9182..97b979f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This library allows you to quickly and easily use the Buffer Web API v1 via PHP. ## Getting Started ### Prerequisites -- PHP >=7.2 +- PHP >=8.2 ### Installation Run into the terminal the next command diff --git a/composer.json b/composer.json index cc20af9..b641534 100644 --- a/composer.json +++ b/composer.json @@ -8,17 +8,18 @@ "Bufferapp" ], "require": { - "php": ">=7.2", + "php": ">=8.2", "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "guzzlehttp/guzzle": "^7" + "symfony/http-client": "^7.2" }, "require-dev": { "squizlabs/php_codesniffer": "3.*", "swaggest/json-diff": "^3.4", - "phpunit/phpunit": "^8.3" + "phpunit/phpunit": "^8.3", + "phpstan/phpstan": "^2.0" }, "type": "library", "autoload": { diff --git a/lib/Http/Client.php b/lib/Http/Client.php index 3d19676..bfaa623 100644 --- a/lib/Http/Client.php +++ b/lib/Http/Client.php @@ -3,13 +3,20 @@ namespace BufferSDK\Http; use BufferSDK\Auth\AuthorizationTokenInterface; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; class Client implements ClientInterface { /** @var string */ protected $baseURL = 'https://api.bufferapp.com/1/'; - /** @var \GuzzleHttp\Client */ + /** @var HttpClientInterface */ private $httpClient; /** @@ -17,35 +24,26 @@ class Client implements ClientInterface */ public function __construct(AuthorizationTokenInterface $auth) { - $this->httpClient = new \GuzzleHttp\Client([ - 'base_uri' => $this->baseURL, - 'handler' => new \GuzzleHttp\Handler\CurlHandler(), - \GuzzleHttp\RequestOptions::HEADERS => [ + $this->httpClient = HttpClient::create([ + 'headers' => [ 'Authorization' => 'Bearer ' . $auth->getAccessToken(), - ], + ] ]); } /** * Create Http Request and send the request. * - * @param string $method - * @param string $endpoint - * @param array $options - * - * @return array - * - * @throws \GuzzleHttp\Exception\GuzzleException + * @throws TransportExceptionInterface + * @throws DecodingExceptionInterface When the body cannot be decoded to an array + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true */ public function createHttpRequest(string $method, string $endpoint, array $options = []): array { - $response = $this->httpClient->request($method, $endpoint, $options); - $responseBody = json_decode($response->getBody()->getContents(), true, 512, JSON_BIGINT_AS_STRING); - - if (\JSON_ERROR_NONE !== json_last_error()) { - throw new \Exception(json_last_error_msg() . sprintf(' for "%s".', $method), json_last_error()); - } - - return $responseBody; + $response = $this->httpClient->request($method, $this->baseURL . $endpoint, $options); + return $response->toArray(); } } diff --git a/lib/Service/ProfileService.php b/lib/Service/ProfileService.php index 1f1b78a..3d5c5ad 100644 --- a/lib/Service/ProfileService.php +++ b/lib/Service/ProfileService.php @@ -74,7 +74,7 @@ public function updateSchedule(string $profileID, Schedule $schedule): array 'POST', "profiles/{$profileID}/schedules/update.json", [ - \GuzzleHttp\RequestOptions::JSON => $payload, + 'body' => $payload, ] ); } diff --git a/lib/Service/UpdateService.php b/lib/Service/UpdateService.php index 6bb6f92..824ef57 100644 --- a/lib/Service/UpdateService.php +++ b/lib/Service/UpdateService.php @@ -69,7 +69,7 @@ public function reorderUpdates(string $profileID, array $order, ?int $offset = n 'POST', "profiles/{$profileID}/updates/reorder.json", [ - \GuzzleHttp\RequestOptions::JSON => [ + 'body' => [ 'order' => $order, 'offset' => $offset, 'utc' => $utc, @@ -94,7 +94,7 @@ public function shuffleUpdates(string $profileID, ?int $count = null, bool $utc 'POST', "profiles/{$profileID}/updates/shuffle.json", [ - \GuzzleHttp\RequestOptions::JSON => [ + 'body' => [ 'count' => $count, 'utc' => $utc, ], @@ -130,7 +130,7 @@ public function createUpdate(Update $update): array 'POST', 'updates/create.json', [ - \GuzzleHttp\RequestOptions::JSON => $payload, + 'body' => $payload, ] ); } @@ -160,7 +160,7 @@ public function updateUpdate(Update $update): array 'POST', "updates/{$update->id}/update.json", [ - \GuzzleHttp\RequestOptions::JSON => $payload, + 'body' => $payload, ] ); }