Phase 1 sub-commit 7: CI quality job
Some checks failed
CI / Quality (push) Has been cancelled

PHPStan (level 6 + symfony extension) and PHP CS Fixer (Symfony +
PHP83Migration ruleset) configs at framework/php/. composer.json
exposes phpstan / cs:check / cs:fix / phpunit / quality scripts.
PHPStan-clean across the bundle; cs:check is happy after auto-fix
applied @Symfony idioms (yoda, leading-backslash JSON_*, blank-line
before return). Test mocks consolidated into a HubSpy helper to keep
PHPStan happy about by-ref captures.

Skeleton's Makefile target `quality` chains `composer quality` (in
framework/php/) with cmake's all_qmllint target. Local run is green —
11 tests / 32 assertions, no PHPStan errors, cs-fixer clean, qmllint
emits advisory warnings only.

Layout fix in skeleton's Main.qml: status-dot Rectangles inside
RowLayout now use Layout.preferredWidth/Height instead of width/height
to satisfy Quick.layout-positioning checks.

.gitea/workflows/ci.yml replaces the placeholder with a real `quality`
job: setup-php, composer install (cached), the four PHP checks, Qt 6
via install-qt-action (cached), QML module build, qmllint via the
all_qmllint CMake target. Workflow exists from this commit onward
even if a runner isn't provisioned yet.

bridge:doctor lost the Publisher dependency since it was only used as
a "service is wired" marker — the command being injectable already
proves that.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-02 02:15:06 +02:00
parent d671b26cac
commit 7323b9affe
14 changed files with 198 additions and 108 deletions

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace PhpQml\Bridge\Command;
use PhpQml\Bridge\Publisher;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -23,7 +22,6 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire;
final class BridgeDoctorCommand extends Command
{
public function __construct(
private readonly Publisher $publisher,
#[Autowire('%env(default::BRIDGE_TOKEN)%')]
private readonly string $bridgeToken,
#[Autowire('%env(default::MERCURE_URL)%')]
@@ -47,34 +45,30 @@ final class BridgeDoctorCommand extends Command
$io = new SymfonyStyle($input, $output);
$io->title('php-qml bridge — readiness checks');
// PHP version + extensions are enforced by composer, so we don't
// re-check them runtime — would always be true on a working install.
$checks = [
['PHP version >= 8.3',
PHP_VERSION_ID >= 80300,
'Upgrade PHP to 8.3 or newer; the bundle requires it.'],
['ext-curl available',
extension_loaded('curl'),
'Install the PHP curl extension.'],
['ext-json available',
extension_loaded('json'),
'Install the PHP json extension.'],
['Publisher service wired',
$this->publisher instanceof Publisher,
'Bundle services failed to load — register PhpQml\\Bridge\\BridgeBundle in config/bundles.php.'],
['BRIDGE_TOKEN env set',
$this->bridgeToken !== '',
'' !== $this->bridgeToken,
'Set BRIDGE_TOKEN in .env.local; the Qt host expects this as the bearer token.'],
['MERCURE_URL env set',
$this->mercureUrl !== '',
'' !== $this->mercureUrl,
'Set MERCURE_URL in .env, e.g. http://127.0.0.1:8765/.well-known/mercure.'],
['MERCURE_PUBLISHER_JWT_KEY env set',
$this->mercurePublisherKey !== '',
'' !== $this->mercurePublisherKey,
'Set MERCURE_PUBLISHER_JWT_KEY in .env.local; mercure-bundle uses it to sign publish requests.'],
['MERCURE_SUBSCRIBER_JWT_KEY env set',
$this->mercureSubscriberKey !== '',
'' !== $this->mercureSubscriberKey,
'Set MERCURE_SUBSCRIBER_JWT_KEY in .env.local; or rely on the Caddy `anonymous` directive in dev mode.'],
];
$rows = [];
$rows = [];
$allPass = true;
foreach ($checks as [$label, $ok, $hint]) {
$rows[] = [
@@ -100,10 +94,12 @@ final class BridgeDoctorCommand extends Command
if ($allPass) {
$io->success('All checks passed. Bridge is ready.');
return Command::SUCCESS;
}
$io->warning('Some checks failed — fix the items above before running the bridge.');
return Command::FAILURE;
}
@@ -120,17 +116,19 @@ final class BridgeDoctorCommand extends Command
],
]);
$body = @file_get_contents($url, false, $ctx);
if ($body === false) {
if (false === $body) {
$err = error_get_last()['message'] ?? 'unknown error';
return ['ok' => false, 'detail' => $err];
}
$statusLine = $http_response_header[0] ?? '';
preg_match('#HTTP/\S+\s+(\d+)#', $statusLine, $m);
$status = (int) ($m[1] ?? 0);
return [
'ok' => $status === 200,
'ok' => 200 === $status,
'status' => $status,
'detail' => $status === 200 ? '' : "expected 200, got {$status}",
'detail' => 200 === $status ? '' : "expected 200, got {$status}",
];
}
}