Phase 3 sub-commit 3: examples/todo POC app, built via the makers

Standalone Composer/CMake project under examples/todo/ derived from the
skeleton, demonstrating every Phase 3 architectural primitive in a
non-trivial app. All cross-side wiring is maker-generated; no
handwritten bridge glue.

Generated and customised:

  - src/Entity/Todo.php — make:bridge:resource Todo (UUIDv7 id)
  - src/Controller/TodoController.php — make:bridge:resource Todo (CRUD)
  - src/Controller/MarkAllDoneController.php — make:bridge:command
    MarkAllDone, body filled in to flip done=true on every row
  - qml/TodoList.qml — make:bridge:resource Todo (starter ListView)
  - qml/TodoWindow.qml — make:bridge:window Todo, body customised to
    embed a read-only mirror of the same ReactiveListModel

The Phase 1 ping demo is dropped from this app — it doesn't fit the
todo flow and nothing in Main.qml references it.

Main.qml is the real list UI:

  - Add input + button (POST /api/todos with optimistic-friendly key).
  - Per-row CheckBox + delete button (PATCH/DELETE via
    todoModel.invoke() with `pending` role driving opacity).
  - "Mark all done" button (POST /api/mark-all-done).
  - "Open second window" button (Component { TodoWindow {} } pattern).

Build / run delegated to the same Makefile shape as the skeleton, with
SCRIPT_DIR/QT_BIN updated for the renamed binary (build/qml/todo).
composer.json's path repo points at ../../../framework/php (one level
deeper than the skeleton's path repo).

Verified end-to-end with offscreen QPA: POST/PATCH/DELETE on /api/todos
all round-trip, /api/mark-all-done flips every row, Mercure dual-
publishes on every change. Clean shutdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-02 15:22:36 +02:00
parent 9c97984bc9
commit 15f9aa032e
28 changed files with 6793 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
PhpQml\Bridge\BridgeBundle::class => ['all' => true],
];

View File

@@ -0,0 +1,38 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# SQLite default for dev — see .env.
# Apps swap this to Postgres / MySQL when they outgrow it.
types:
uuid: Symfony\Bridge\Doctrine\Types\UuidType
orm:
validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
identity_generation_preferences:
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
auto_mapping: true
mappings:
App:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
when@prod:
doctrine:
orm:
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View File

@@ -0,0 +1,4 @@
doctrine_migrations:
migrations_paths:
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false

View File

@@ -0,0 +1,13 @@
framework:
secret: '%env(APP_SECRET)%'
http_method_override: false
handle_all_throwables: true
php_errors:
log: true
router:
utf8: true
serializer:
enabled: true
property_info:
enabled: true
test: false

View File

@@ -0,0 +1,9 @@
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%'
public_url: '%env(MERCURE_PUBLIC_URL)%'
jwt:
secret: '%env(MERCURE_JWT_SECRET)%'
publish: ['*']
subscribe: ['*']

View File

@@ -0,0 +1,18 @@
security:
providers:
bridge_provider:
memory:
users:
bridge: { roles: ['ROLE_BRIDGE'] }
firewalls:
unauth:
pattern: ^/(healthz|\.well-known/mercure)
security: false
api:
pattern: ^/api
stateless: true
provider: bridge_provider
custom_authenticators:
- PhpQml\Bridge\SessionAuthenticator
access_control:
- { path: ^/api, roles: ROLE_BRIDGE }

View File

@@ -0,0 +1,11 @@
php_qml_bridge:
resource:
path: ../vendor/php-qml/bridge/src/Controller/
namespace: PhpQml\Bridge\Controller
type: attribute
app_controllers:
resource:
path: ../src/Controller/
namespace: App\Controller
type: attribute

View File

@@ -0,0 +1,11 @@
parameters:
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/Kernel.php'