Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,11 @@ jobs:
- name: Install dependencies
run: composer install --no-progress --no-suggest --no-interaction

- name: Run ECS
run: ./vendor/bin/ecs check

- name: Run PHPStan
run: ./vendor/bin/phpstan analyse

- name: Run PHPUnit tests
run: ./vendor/bin/phpunit
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.PHONY: lint lint-ecs lint-phpstan test

lint: lint-ecs lint-phpstan

lint-ecs:
./vendor/bin/ecs check

lint-phpstan:
./vendor/bin/phpstan analyse

test:
./vendor/bin/phpunit
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
},
"require-dev": {
"phpunit/phpunit": "^11",
"symplify/easy-coding-standard": "^12.3"
"symplify/easy-coding-standard": "^12.3",
"phpstan/phpstan": "^2"
},
"autoload": {
"psr-4": {
Expand Down
55 changes: 54 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 61 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
parameters:
ignoreErrors:
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Activity.php

-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Organization/Address.php

-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Model/Organization/Organization.php

-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Organization/Organization.php

-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Organization/Profile.php

-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Ref/Resolver.php

-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/SetupOptions.php

-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Team/Team.php

-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/UserAccess/ProjectUserAccess.php

-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:post\(\)\.$#'
identifier: method.notFound
count: 1
path: src/PlatformClient.php
Comment on lines +2 to +61
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

phpstan-baseline.neon uses tab indentation, but .editorconfig sets indent_style=space for all files. To keep formatting consistent (and avoid future whitespace churn), consider regenerating or reformatting the baseline using spaces.

Suggested change
ignoreErrors:
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Activity.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Organization/Address.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Model/Organization/Organization.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Organization/Organization.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Organization/Profile.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Ref/Resolver.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/SetupOptions.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Team/Team.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/UserAccess/ProjectUserAccess.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:post\(\)\.$#'
identifier: method.notFound
count: 1
path: src/PlatformClient.php
ignoreErrors:
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Activity.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Organization/Address.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Model/Organization/Organization.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Organization/Organization.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Organization/Profile.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Ref/Resolver.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:get\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/SetupOptions.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/Team/Team.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:patch\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Model/UserAccess/ProjectUserAccess.php
-
message: '#^Call to an undefined method GuzzleHttp\\ClientInterface\:\:post\(\)\.$#'
identifier: method.notFound
count: 1
path: src/PlatformClient.php

Copilot uses AI. Check for mistakes.
12 changes: 12 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
includes:
- phpstan-baseline.neon

parameters:
level: 5
paths:
- src
- tests
ignoreErrors:
# Deliberate pattern: resource classes use new static() for polymorphism.
-
identifier: new.static
10 changes: 10 additions & 0 deletions src/Connection/ConnectorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ public function getClient(): ClientInterface;
*/
public function setApiToken(string $token, string $type);

/**
* Get the connector configuration.
*/
public function getConfig(): array;

/**
* Returns the access token saved in the session, if any.
*/
public function getAccessToken(): ?string;

Comment on lines +56 to +65
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding new required methods to ConnectorInterface (getConfig(), getAccessToken()) is a backwards-incompatible change for any downstream code that implements this interface. If maintaining BC is important, consider introducing a new extended interface (e.g., ConfigAwareConnectorInterface) or keeping these methods off the interface and using instanceof/method_exists checks where needed.

Copilot uses AI. Check for mistakes.
/**
* Get the configured API gateway URL (without trailing slash).
*/
Expand Down
6 changes: 3 additions & 3 deletions src/Model/ActivityLog/LogItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function __toString()
*/
public static function singleFromJson(string $str): self|false
{
$data = static::decode($str);
$data = self::decode($str);
if (isset($data['data']['timestamp'], $data['data']['message'])) {
$id = isset($data['_id']) ? (string) $data['_id'] : '';
return new static($data['data']['timestamp'], $data['data']['message'], $id);
Expand All @@ -43,7 +43,7 @@ public static function singleFromJson(string $str): self|false
}

/**
* @return static[]
* @return self[]
*@deprecated use LogItem::multipleFromJsonStreamWithSeal() instead
*/
public static function multipleFromJsonStream(string $str): array
Expand Down Expand Up @@ -77,7 +77,7 @@ public static function multipleFromJsonStreamWithSeal(string $str): array
if ($line === '') {
continue;
}
$data = static::decode($line);
$data = self::decode($line);
if (is_array($data)) {
if (! empty($data['seal'])) {
$seal = true;
Expand Down
7 changes: 5 additions & 2 deletions src/Model/AutoscalingSettings.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<?php

declare(strict_types=1);

namespace Platformsh\Client\Model;

/**
* Represents environment autoscaling settings.
*
*/
class AutoscalingSettings extends ApiResourceBase {}
class AutoscalingSettings extends ApiResourceBase
{
}
15 changes: 0 additions & 15 deletions src/Model/Backups/RestoreOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,30 @@ class RestoreOptions

private ?string $resourcesInit;

/**
* @return RestoreOptions
*/
public function setEnvironmentName(?string $environmentName): static
{
$this->environmentName = $environmentName;
return $this;
}

/**
* @return RestoreOptions
*/
public function setBranchFrom(?string $branchFrom): static
{
$this->branchFrom = $branchFrom;
return $this;
}

/**
* @return RestoreOptions
*/
public function setRestoreCode(?bool $restoreCode): static
{
$this->restoreCode = $restoreCode;
return $this;
}

/**
* @return RestoreOptions
*/
public function setRestoreResources(?bool $restoreResources): static
{
$this->restoreResources = $restoreResources;
return $this;
}

/**
* @return RestoreOptions
*/
public function setResourcesInit(?string $init): static
{
$this->resourcesInit = $init;
Expand Down
5 changes: 1 addition & 4 deletions src/Model/Git/Tree.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,11 @@ public function getBlob(string $path): false|Blob
if ($object === false) {
return false;
}
if ($object instanceof Blob) {
return $object;
}
if ($object instanceof self) {
throw new GitObjectTypeException('The requested file is a directory', $path);
}

return false;
return $object;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Model/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ public function clearBuildCache(): Result
/**
* Returns system information about the project, e.g. the API version.
*/
public function systemInformation(): System
public function systemInformation(): System|false
{
return System::get($this->getLink('#system'), '', $this->client);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Model/Ref/Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function resolveReferences(array $data): array
return $data;
}
foreach ($data['_links'] as $key => $link) {
if (str_starts_with($key, 'ref:') && ($parts = \explode(':', $key, 3)) && \count($parts) === 3) {
if (str_starts_with($key, 'ref:') && \count($parts = \explode(':', $key, 3)) === 3) {
$set = $parts[1];
$linkUri = Utils::uriFor($link['href']);
$absoluteUrl = Utils::uriFor($this->baseUrl)->withPath($linkUri->getPath())->withQuery($linkUri->getQuery());
Expand Down
2 changes: 1 addition & 1 deletion src/Model/ResourceWithReferences.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ protected static function resolveReferences(Resolver $resolver, array $data): ar
$data = $resolver->resolveReferences($data);
} catch (\Exception $e) {
$message = $e->getMessage();
if ($e instanceof BadResponseException && $e->getResponse()) {
if ($e instanceof BadResponseException) {
$message = \sprintf('status code %d', $e->getResponse()->getStatusCode());
}
\trigger_error('Unable to resolve references: ' . $message, E_USER_WARNING);
Expand Down
2 changes: 1 addition & 1 deletion src/Model/Result.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function getActivities(): array
*/
public function getEntity(): ApiResourceBase
{
if (! isset($this->data['_embedded']['entity']) || ! isset($this->resourceClass)) {
if (! isset($this->data['_embedded']['entity'])) {
throw new \Exception('No entity found in result');
}

Expand Down
6 changes: 5 additions & 1 deletion src/Model/Settings.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<?php

declare(strict_types=1);

namespace Platformsh\Client\Model;

/**
* Represents environment settings.
*
* @property-read bool $enable_manual_deployments
*/
class Settings extends ApiResourceBase {}
class Settings extends ApiResourceBase
{
}
2 changes: 1 addition & 1 deletion src/Model/Subscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public static function create(array $body, string $collectionUrl, ClientInterfac
{
$result = parent::create($body, $collectionUrl, $client);

return new self($result->getData(), $collectionUrl, $client);
return new static($result->getData(), $collectionUrl, $client);
}

/**
Expand Down
12 changes: 9 additions & 3 deletions src/PlatformClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,6 @@ public function getOrganizationById(string $id): Organization|false
* @param string $country An ISO 2-letter country code.
* @param string $owner The organization owner ID. Leave empty to use the current user.
* @param string $type The organization type. Leave blank to use the default.
*
* @return Organization
*/
public function createOrganization(string $name, string $label = '', string $country = '', string $owner = '', string $type = ''): Organization
{
Expand Down Expand Up @@ -643,6 +641,9 @@ public function getTeam(string $id, Organization $organization = null): false|Te
*/
protected function locateProject(string $id): false|string
{
if (! $this->connector instanceof Connector) {
return false;
}
$url = rtrim($this->connector->getAccountsEndpoint(), '/') . '/projects/' . rawurlencode($id);
try {
$result = $this->simpleGet($url);
Expand Down Expand Up @@ -678,7 +679,12 @@ protected function cleanRequest(array $request): array
*/
private function apiUrl(): string
{
return $this->connector->getApiUrl() ?: rtrim($this->connector->getAccountsEndpoint(), '/');
$url = $this->connector->getApiUrl();
if ($url === '' && $this->connector instanceof Connector) {
$url = rtrim($this->connector->getAccountsEndpoint(), '/');
}

return $url;
}

/**
Expand Down
Loading
Loading