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>
100 lines
3.2 KiB
PHP
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.
|
|
}
|
|
}
|