Files
php-qml/framework/php/src/Maker/BridgeWindowMaker.php
magdev 0cceefc890 v0.1.3: audit-driven non-breaking fixes
Three bugs surfaced by the post-v0.1.2 architecture audit:

- bridge.qml_path is now actually configurable. BridgeBundle::configure
  defines the qml_path scalar node (default ../qml/); loadExtension
  exposes it as the bridge.qml_path container parameter; services.yaml
  binds it into BridgeResourceMaker + BridgeWindowMaker. Apps override
  with `config/packages/bridge.yaml`. The existing maker docstrings
  claimed this worked already — they lied; now they don't.

- SessionAuthenticator implements AuthenticationEntryPointInterface and
  routes the no-token entry-point path through the same problem+json
  helper as onAuthenticationFailure, so QML's RestClient sees one error
  shape regardless of which firewall path was taken. Test added.

- CorrelationKeyListener::onTerminate guards on isMainRequest() now,
  matching onRequest's existing guard. No user-visible impact in
  worker mode (no sub-requests emitted), but the asymmetry was a
  defensive bug that would corrupt optimistic-update reconciliation.

PLAN.md §13 gains a v0.1.3 section + folds the audit's API-surface
items (PublisherInterface / ModelPublisherInterface / BridgeOp enum /
maker DRY / DTO-shaped scaffold) into v0.2.0. CHANGELOG.md mirrors.

PHPStan + cs-fixer + PHPUnit (17/17) + maker snapshot tests all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:31:54 +02:00

100 lines
3.2 KiB
PHP

<?php
declare(strict_types=1);
namespace PhpQml\Bridge\Maker;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
/**
* `make:bridge:window <Name>` — emits a top-level QML Window that
* wraps `AppShell` and a content slot. Application code opens it via
* `Qt.createComponent("<Name>Window.qml")` (or by importing it) for
* the first window and as many extra instances as it wants for the
* multi-window test from PLAN.md §9 / §13 Phase 3.
*
* Generated file goes to `qml_path` (default: `../qml/`, set via
* `config/packages/bridge.yaml`: `bridge: { qml_path: ../qml/ }`).
*/
final class BridgeWindowMaker extends AbstractMaker
{
public function __construct(
private readonly string $qmlPath = '../qml/',
) {
}
public static function getCommandName(): string
{
return 'make:bridge:window';
}
public static function getCommandDescription(): string
{
return 'Generate a top-level QML Window scaffold (AppShell + content slot).';
}
public function configureCommand(Command $command, InputConfiguration $inputConfig): void
{
$command
->addArgument(
'name',
InputArgument::OPTIONAL,
'Singular name of the window (e.g. Todo, Settings).',
)
->setHelp(
"Creates one file:\n\n"
." • <info>{qml_path}/<Name>Window.qml</info> — Window subclass with AppShell\n"
);
}
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
{
if (null === $input->getArgument('name')) {
$name = $io->ask('Window name?', null, static function (?string $v): string {
if (null === $v || '' === trim($v)) {
throw new \RuntimeException('Window name cannot be empty.');
}
return ucfirst(trim($v));
});
$input->setArgument('name', $name);
}
}
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
{
$rawName = (string) $input->getArgument('name');
$singular = ucfirst(Str::asCamelCase($rawName));
$resource = strtolower($singular);
$vars = [
'singular' => $singular,
'resource' => $resource,
];
$target = rtrim($this->qmlPath, '/').'/'.$singular.'Window.qml';
$generator->generateFile(
$target,
__DIR__.'/templates/Window.tpl.php',
$vars,
);
$generator->writeChanges();
$this->writeSuccessMessage($io);
$io->text("Window scaffold at <info>{$target}</info>.");
}
public function configureDependencies(DependencyBuilder $dependencies): void
{
// Pure-QML output — no PHP runtime deps.
}
}