Skip to content
Merged
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
34 changes: 26 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ on:
pull_request:

jobs:
# Nothing here reads a database, so it runs once instead of in each leg below.
backend-static:
name: Backend (static)
# Neither static job reads a database, so each runs once instead of in each
# leg below. PHPStan is most of the static time, so the rest runs beside it.
backend-phpstan:
name: Backend (PHPStan)
runs-on: ubuntu-latest

defaults:
Expand All @@ -20,23 +21,40 @@ jobs:

- uses: ./.github/actions/setup-backend

- name: PHPCS (PSR-12)
run: composer cs

- name: Warm cache for PHPStan
run: php bin/console cache:warmup

# PHPStan drops whatever a change invalidated, so any earlier run's cache
# is a safe start. It must stay before tramp:update, which rewrites vendor.
# is a safe start. No tramp:update in this job, so vendor stays lock-pinned.
- name: Restore PHPStan's result cache
uses: actions/cache@v6
with:
path: /tmp/phpstan
key: phpstan-${{ github.sha }}
restore-keys: phpstan-

# -vvv prints why a restored result cache was not used (#1871).
- name: PHPStan
run: composer stan
run: composer stan -- -vvv

backend-lint:
name: Backend (lint)
runs-on: ubuntu-latest

defaults:
run:
working-directory: backend

steps:
- uses: actions/checkout@v5

- uses: ./.github/actions/setup-backend

- name: PHPCS (PSR-12)
run: composer cs

- name: Warm cache for symfony-lsp
run: php bin/console cache:warmup

# PHPMD codesize was a local-only discipline gate and silently broke once
# (pdepend could not parse PHP 8.4 syntax, #183) — so the sweep now runs
Expand Down
4 changes: 2 additions & 2 deletions backend/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@
"dama/doctrine-test-bundle": "^8.6",
"infection/infection": "^0.34.1",
"phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^2.2.5",
"phpstan/phpstan": "^2.3.4",
"phpstan/phpstan-doctrine": "^2.0.28",
"phpstan/phpstan-symfony": "^2.0.20",
"phpstan/phpstan-symfony": "^2.1.1",
"phptramp/phptramp": "dev-develop",
"phpunit/phpunit": "^12.5.31",
"squizlabs/php_codesniffer": "^3.13.6",
Expand Down
26 changes: 13 additions & 13 deletions backend/composer.lock

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

2 changes: 1 addition & 1 deletion backend/src/Service/Bluesky/Support/QuotedViewRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private static function nestedQuoteOf(JsonNodeModel $embed): ?QuotedPostReferenc
};
$url = $nested === null ? null : AtPostUri::webUrl($nested->string('uri') ?? '');

return $url === null ? null : new QuotedPostReference($url, $nested?->node('author')->string('handle'));
return $url === null ? null : new QuotedPostReference($url, $nested->node('author')->string('handle'));
}

private function __construct()
Expand Down
2 changes: 1 addition & 1 deletion backend/src/Service/Discovery/FeedLinkScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private function advertisedFeeds(HTMLDocument $document, PageUrls $pageUrls, Lin
foreach ($document->querySelectorAll('link[rel~="alternate" i][type]') as $link) {
$format = self::ADVERTISED_TYPES[$this->attribute($link, 'type')] ?? null;
$url = null === $format ? null : $this->subscribableUrl($link, $pageUrls);
if (null === $url || null === $format || isset($candidates[$url])) {
if (null === $url || isset($candidates[$url])) {
continue;
}

Expand Down
2 changes: 2 additions & 0 deletions backend/src/Service/Grafana/GrafanaSettingsCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public function __construct(private CacheItemPoolInterface $grafanaSettingsCache
* @param callable():GrafanaSettingsSnapshotModel $loadFromDatabase
*
* @throws InvalidArgumentException
*
* @phpstan-impure
*/
public function remember(callable $loadFromDatabase): GrafanaSettingsSnapshotModel
{
Expand Down
1 change: 1 addition & 0 deletions backend/src/Service/Image/ImageVerifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public function __construct(
) {
}

/** @phpstan-impure */
public function verify(Entry $entry): ImageVerifyOutcome
{
$url = $entry->getImage()->getUrl();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function __construct(
) {
}

/** @phpstan-impure */
public function advance(User $user, TickDriver $driver = TickDriver::Poll): RecommendationRunReportModel
{
$held = $this->tickLock->acquire($user, $this->lockTtl->secondsFor($user));
Expand Down
7 changes: 4 additions & 3 deletions backend/tests/Controller/Api/EntryPageParametersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,10 @@ public function testAbsentPageParametersServeTheNewestFirstPageAtTheDefaultLimit
private function assertValidationErrorOn(KernelBrowser $client, string $field): void
{
$this->assertRejected($client, 422);
self::assertSame('validation_error', $this->payload($client)['type']);
self::assertIsArray($this->payload($client)['errors']);
self::assertArrayHasKey($field, $this->payload($client)['errors']);
$problem = $this->payload($client);
self::assertSame('validation_error', $problem['type']);
self::assertIsArray($problem['errors']);
self::assertArrayHasKey($field, $problem['errors']);
}

/** @return list<string> */
Expand Down
6 changes: 5 additions & 1 deletion backend/tests/Controller/Api/EntryTagScopeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,11 @@ private function tag(User $user, string $name, ?Tag $parent): Tag
return $tag;
}

/** @return list<string> */
/**
* @return list<string>
*
* @phpstan-impure
*/
private function titlesOfResponse(): array
{
$entries = $this->payload($this->client)['entries'];
Expand Down
1 change: 1 addition & 0 deletions backend/tests/Service/Fetch/Pass/FetchQueueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ private function attempt(string $url): FetchAttemptModel
return FetchAttemptModel::start(0, FeedTickets::of($url));
}

/** @phpstan-impure */
private function runnable(FetchQueue $queue): FetchAttemptModel
{
$attempt = $queue->takeRunnable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,10 @@ public function testReadingTheAnswerRepeatedlyIsStable(): void

$reader->consume($this->contentEvent('once'));

self::assertSame('once', $reader->assistantContent());
self::assertSame('once', $reader->assistantContent());
$firstRead = $reader->assistantContent();

self::assertSame('once', $firstRead);
self::assertSame($firstRead, $reader->assistantContent());
}

/**
Expand Down
6 changes: 5 additions & 1 deletion backend/tests/Support/ApiTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ protected function assertRejected(KernelBrowser $client, int $status): void
self::assertSame('application/problem+json', $response->headers->get('Content-Type'));
}

/** @return array<string, mixed> */
/**
* @return array<string, mixed>
*
* @phpstan-impure
*/
protected function payload(KernelBrowser $client): array
{
$decoded = json_decode((string) $client->getResponse()->getContent(), true);
Expand Down
6 changes: 5 additions & 1 deletion backend/tests/Support/TranscriptSearchFixtures.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ public function searchForTranscriptOnlyTerm(): SavedSearch
return $search;
}

/** @return list<int> */
/**
* @return list<int>
*
* @phpstan-impure
*/
public function memberEntryIds(SavedSearch $search): array
{
/** @var list<int|string> $ids */
Expand Down
Loading