` — generates the three files needed to * expose a Doctrine entity as a reactive bridge resource: * * - src/Entity/.php — `#[BridgeResource]` + `#[ORM\Entity]` * - src/Controller/Controller.php — CRUD on `/api/{plural}` * - {qml_path}/List.qml — starter `ReactiveListModel` * * The Doctrine subscriber installed by the bundle picks the entity up * automatically — no per-resource listener is generated. The QML snippet * goes to `qml_path` (default: `../qml/`, configurable via the bundle's * `qml_path` option in services.yaml). * * See PLAN.md §8 (*Custom makers*). */ final class BridgeResourceMaker extends AbstractMaker { public function __construct( private readonly string $qmlPath = '../qml/', ) { } public static function getCommandName(): string { return 'make:bridge:resource'; } public static function getCommandDescription(): string { return 'Generate a #[BridgeResource] entity, CRUD controller, and QML snippet.'; } public function configureCommand(Command $command, InputConfiguration $inputConfig): void { $command ->addArgument( 'name', InputArgument::OPTIONAL, 'Singular name of the resource (e.g. Todo).', ) ->addOption( 'int-id', null, InputOption::VALUE_NONE, 'Use auto-incrementing int IDs instead of the default UUIDv7.', ) ->setHelp( "The maker creates three files:\n\n" ." • src/Entity/Todo.php — Doctrine entity tagged with #[BridgeResource]\n" ." • src/Controller/TodoController.php — CRUD on /api/todos\n" ." • {qml_path}/TodoList.qml — starter ReactiveListModel snippet\n\n" ."After the maker, run bin/console make:migration and apply it.\n" ); } public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void { if (null === $input->getArgument('name')) { $name = $io->ask('What is the resource name (e.g. Todo)?', null, static function (?string $v): string { if (null === $v || '' === trim($v)) { throw new \RuntimeException('Resource 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'); $useUuid = !(bool) $input->getOption('int-id'); $singular = ucfirst(Str::asCamelCase($rawName)); $pluralCamel = Str::singularCamelCaseToPluralCamelCase($singular); $resource = strtolower($singular); $pluralUnder = strtolower(preg_replace('/(?createClassNameDetails( $singular, 'Entity\\', ); $controllerFqcn = $generator->createClassNameDetails( $singular, 'Controller\\', 'Controller', ); $vars = [ 'singular' => $singular, 'plural' => $pluralUnder, 'resource' => $resource, 'route' => $route, 'entity_short' => $entityFqcn->getShortName(), 'entity_fqcn' => $entityFqcn->getFullName(), 'controller_fqcn' => $controllerFqcn->getFullName(), 'use_uuid' => $useUuid, ]; $generator->generateFile( 'src/Entity/'.$entityFqcn->getShortName().'.php', __DIR__.'/templates/Entity.tpl.php', $vars, ); $generator->generateFile( 'src/Controller/'.$controllerFqcn->getShortName().'.php', __DIR__.'/templates/Controller.tpl.php', $vars, ); // QML snippet — outside the Symfony project root, so we use a // path relative to the project's working dir. $qmlTarget = rtrim($this->qmlPath, '/').'/'.$singular.'List.qml'; $generator->generateFile( $qmlTarget, __DIR__.'/templates/QmlSnippet.tpl.php', $vars, ); $generator->writeChanges(); $this->writeSuccessMessage($io); $io->text([ 'Next:', ' 1) bin/console make:migration', ' 2) bin/console doctrine:migrations:migrate -n', " 3) Use {$singular}List.qml from your QML.", ]); } public function configureDependencies(DependencyBuilder $dependencies): void { $dependencies->addClassDependency(ORM\Entity::class, 'doctrine/orm'); $dependencies->addClassDependency(BridgeResource::class, 'php-qml/bridge'); $dependencies->addClassDependency(Route::class, 'symfony/routing'); $dependencies->addClassDependency(JsonResponse::class, 'symfony/http-foundation'); $dependencies->addClassDependency(Request::class, 'symfony/http-foundation'); $dependencies->addClassDependency(EntityManagerInterface::class, 'doctrine/orm'); $dependencies->addClassDependency(SerializerInterface::class, 'symfony/serializer'); $dependencies->addClassDependency(Uuid::class, 'symfony/uid'); } }