--- phase: 06-admin-base plan: 02 type: execute wave: 1 depends_on: ["06-01"] files_modified: - autoload/admin/Support/TableListRequestFactory.php - autoload/admin/Support/Forms/FormRequestHandler.php - autoload/admin/Support/Forms/FormFieldRenderer.php autonomous: true delegation: off --- ## Goal Domknąć Phase 6 (Admin Base Infrastructure) przez przeniesienie z shopPRO 3 klas warstwy Support: `TableListRequestFactory` (parser requestów listy z filtrami/sortowaniem/paginacją), `FormRequestHandler` (obsługa POST/persist/walidacji formularza), `FormFieldRenderer` (renderer HTML pól formularza). Po 06-02 cała infrastruktura Phase 7+ (kontrolery) jest gotowa. ## Purpose Phase 7-13 będą migrować 17 modułów Admin do nowych namespace'ów. Każdy z tych kontrolerów będzie potrzebował: - `TableListRequestFactory::fromRequest()` — uniformowy parsing list z `?page=&per_page=&sort=&filter[]=` - `FormRequestHandler` — uniformowy handler POST → walidacja → persist → redirect - `FormFieldRenderer` — generowanie HTML pól z `FormField` ViewModeli (zastępuje Shared\Html\Html dla nowych formularzy) Bez tych helperów każdy kontroler musiałby duplikować logikę paginacji i obsługi POST. Pakiet 06-01 + 06-02 = pełny fundament Admin\ gotowy do Phase 7. **Decyzja architektoniczna:** Brak `BaseController` — shopPRO używa POJOs z DI (np. `BannerController(BannerRepository, LanguagesRepository)`), nie dziedziczenia. Phase 7+ kontrolery będą zwykłymi klasami z Domain repos i tymi helperami w konstruktorze. ## Output - `autoload/admin/Support/TableListRequestFactory.php` - `autoload/admin/Support/Forms/FormRequestHandler.php` - `autoload/admin/Support/Forms/FormFieldRenderer.php` - `.paul/phases/06-admin-base/06-02-SUMMARY.md` - **BaseCtrl** — Czy tworzymy `BaseController`? → Odpowiedź: NIE. POJOs z DI jak shopPRO. ROADMAP zaktualizowany — wpis BaseController usunięty ze scope Phase 6. - **Helpers** — Które helpery z `Admin/Support/Forms/` w 06-02? → Odpowiedź: Oba — `FormRequestHandler` (159 L) + `FormFieldRenderer` (494 L). Pełny pakiet, żeby Phase 7+ miało gotowe wszystko. - **Filename** — Konwencja nazwy pliku? → Odpowiedź: PSR-4 czyste — `TableListRequestFactory.php` (bez `class.` prefiksu z shopPRO). Zgodne z resztą cmsPRO Domain/Shared (np. `ArticlesRepository.php`). ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md @.paul/codebase/architecture.md ## Prior Work @.paul/phases/06-admin-base/06-01-SUMMARY.md ## Reference Source (shopPRO — wzorzec do skopiowania) @C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\class.TableListRequestFactory.php @C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormRequestHandler.php @C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormFieldRenderer.php ## cmsPRO target paths - `autoload/admin/Support/TableListRequestFactory.php` (`Admin\Support\TableListRequestFactory`) - `autoload/admin/Support/Forms/FormRequestHandler.php` (`Admin\Support\Forms\FormRequestHandler`) - `autoload/admin/Support/Forms/FormFieldRenderer.php` (`Admin\Support\Forms\FormFieldRenderer`) Uwaga: PSR-4 mapping w composer.json (po 06-01) to `"Admin\\": "autoload/admin/"`. Folder na disku jest lowercase `admin/` (legacy collision z PascalCase Windows fs), ale namespace pozostaje PascalCase `Admin\Support`. ## AC-1: Wszystkie 3 klasy ładują się przez PSR-4 ```gherkin Given composer dump-autoload wykonany po stworzeniu plików When `php -r "require 'vendor/autoload.php'; var_dump(class_exists('Admin\\Support\\TableListRequestFactory'), class_exists('Admin\\Support\\Forms\\FormRequestHandler'), class_exists('Admin\\Support\\Forms\\FormFieldRenderer'));"` Then otrzymam `bool(true) bool(true) bool(true)` ``` ## AC-2: TableListRequestFactory parsuje request ```gherkin Given klasa Admin\Support\TableListRequestFactory załadowana When wywołam `TableListRequestFactory::fromRequest([], ['name', 'date_add'], 'date_add')` w smoke teście (z `$_GET = ['page'=>'2','per_page'=>'25','sort'=>'name']`) Then otrzymam tablicę z `page=2`, `perPage=25`, `sortColumn='name'` (lub fallback do default jeśli nie jest w sortableColumns), oraz nieputą `viewFilters`/`queryFilters` ``` ## AC-3: FormRequestHandler i FormFieldRenderer mają poprawne `use` do 06-01 ```gherkin Given pliki utworzone w Admin\Support\Forms When przeczytam pierwsze 10 linii FormRequestHandler.php i FormFieldRenderer.php Then znajdę: - FormRequestHandler: `use Admin\ViewModels\Forms\FormEditViewModel;`, `use Admin\ViewModels\Forms\FormFieldType;`, `use Admin\Validation\FormValidator;` - FormFieldRenderer: `use Admin\ViewModels\Forms\FormEditViewModel;`, `use Admin\ViewModels\Forms\FormField;`, `use Admin\ViewModels\Forms\FormFieldType;` And żaden plik nie ma `namespace admin\` (lowercase) ani `use admin\` (lowercase) ``` ## AC-4: Smoke test integracyjny — handler + renderer + Form VMs współpracują ```gherkin Given wszystkie 3 nowe klasy + Form VMs z 06-01 When utworzę `FormField('email', FormFieldType::EMAIL, 'E-mail')`, opakowuję w `FormEditViewModel`, wywołuję `FormFieldRenderer::renderField($field, $vm)` w smoke teście Then otrzymam string HTML zawierający ` Task 1: Skopiuj TableListRequestFactory do Admin\Support z PSR-4 nazwą pliku autoload/admin/Support/TableListRequestFactory.php Utwórz katalog `autoload/admin/Support/` i skopiuj zawartość ze shopPRO: - Źródło: `C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\class.TableListRequestFactory.php` - Cel: `autoload/admin/Support/TableListRequestFactory.php` (BEZ `class.` prefiksu — PSR-4 wymaga match nazwa pliku = nazwa klasy) Zmiana namespace: `namespace admin\Support;` → `namespace Admin\Support;` (PascalCase pierwszy segment, jak 06-01). Cała reszta klasy (stałe `DEFAULT_PER_PAGE_OPTIONS`, `DEFAULT_PER_PAGE`, metoda statyczna `fromRequest()`, logika parsowania `$_GET` przez `\Shared\Helpers\Helpers::get()`) bez zmian. Avoid: - Pozostawienie prefiksu `class.` w nazwie pliku — psuje PSR-4 - Zmiany sygnatur metod (Phase 7+ zakłada API z shopPRO) - Zmiany domyślnego sortColumn w sygnaturze `php -l autoload/admin/Support/TableListRequestFactory.php` → "No syntax errors detected" `grep -c "^namespace Admin\\\\Support;" autoload/admin/Support/TableListRequestFactory.php` → 1 Po `composer dump-autoload`: `php -r "require 'vendor/autoload.php'; var_dump(class_exists('Admin\\\\Support\\\\TableListRequestFactory'));"` → `bool(true)` AC-1 częściowo (TableListRequestFactory ładuje się). AC-2 (smoke test factory) wykonywany w Task 3. Task 2: Skopiuj FormRequestHandler + FormFieldRenderer do Admin\Support\Forms\ autoload/admin/Support/Forms/FormRequestHandler.php, autoload/admin/Support/Forms/FormFieldRenderer.php Utwórz katalog `autoload/admin/Support/Forms/` i skopiuj 2 pliki ze shopPRO: **FormRequestHandler.php** (159 L): - Źródło: `C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormRequestHandler.php` - Cel: `autoload/admin/Support/Forms/FormRequestHandler.php` - Zmiany namespace/use: - `namespace admin\Support\Forms;` → `namespace Admin\Support\Forms;` - `use admin\ViewModels\Forms\FormEditViewModel;` → `use Admin\ViewModels\Forms\FormEditViewModel;` - `use admin\ViewModels\Forms\FormFieldType;` → `use Admin\ViewModels\Forms\FormFieldType;` - `use admin\Validation\FormValidator;` → `use Admin\Validation\FormValidator;` - Cała reszta logiki (handle POST, persist do `$_SESSION`, redirect, walidacja przez `FormValidator`) bez zmian. **FormFieldRenderer.php** (494 L): - Źródło: `C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormFieldRenderer.php` - Cel: `autoload/admin/Support/Forms/FormFieldRenderer.php` - Zmiany namespace/use: - `namespace admin\Support\Forms;` → `namespace Admin\Support\Forms;` - `use admin\ViewModels\Forms\FormEditViewModel;` → `use Admin\ViewModels\Forms\FormEditViewModel;` - `use admin\ViewModels\Forms\FormField;` → `use Admin\ViewModels\Forms\FormField;` - `use admin\ViewModels\Forms\FormFieldType;` → `use Admin\ViewModels\Forms\FormFieldType;` - JEŻELI plik referuje również `FormTab` lub `FormAction` — analogicznie zaktualizuj `use` - Cała reszta (metody `renderField`, `renderTabs`, `renderLangSection` itp.) bez zmian. Po zapisaniu obu plików: `php composer.phar dump-autoload`. Avoid: - Refaktoringu generowanego HTML (UI templates Phase 7+ liczą na konkretny markup) - Pozostawienia jakichkolwiek `admin\` (lowercase) referencji w obu plikach - Zmiany w inkluzji innych klas spoza Admin\* (Shared\Html, Shared\Helpers) `php -l autoload/admin/Support/Forms/FormRequestHandler.php` → "No syntax errors detected" `php -l autoload/admin/Support/Forms/FormFieldRenderer.php` → "No syntax errors detected" `grep -c "^namespace Admin\\\\Support\\\\Forms;" autoload/admin/Support/Forms/*.php` → 2 `grep -E "^use admin\\\\" autoload/admin/Support/Forms/*.php` → brak wyników (zero lowercase use) Po dump-autoload: `class_exists('Admin\\\\Support\\\\Forms\\\\FormRequestHandler')` i `class_exists('Admin\\\\Support\\\\Forms\\\\FormFieldRenderer')` → `bool(true)` × 2 AC-1 satisfied (wszystkie 3 klasy 06-02 ładują się), AC-3 satisfied (poprawne `use` Admin\... do 06-01). Task 3: Smoke test integracyjny — TableListRequestFactory + FormFieldRenderer z VM (brak modyfikacji — tylko runtime weryfikacja) Uruchom inline PHP smoke test sprawdzający 3 zachowania: **A) TableListRequestFactory parsuje request:** ``` php -r "require 'vendor/autoload.php'; \$_GET = ['page'=>'2','per_page'=>'25','sort'=>'name','status'=>'1']; \$ctx = Admin\Support\TableListRequestFactory::fromRequest( [['key'=>'status','label'=>'Status','type'=>'select','options'=>['1'=>'aktywny']]], ['name','date_add'], 'date_add' ); echo 'page='.\$ctx['page'].' perPage='.\$ctx['perPage'].' sort='.\$ctx['sortColumn'].PHP_EOL; echo 'filters_status='.\$ctx['filters']['status'].PHP_EOL; echo 'OK_FACTORY'.PHP_EOL;" ``` Oczekuję: `page=2 perPage=25 sort=name`, `filters_status=1`, `OK_FACTORY`. **B) FormFieldRenderer renderuje pole:** ``` php -r "require 'vendor/autoload.php'; \$f = new Admin\ViewModels\Forms\FormField('email', Admin\ViewModels\Forms\FormFieldType::EMAIL, 'E-mail', 'a@b.c', 'main', true); \$vm = new Admin\ViewModels\Forms\FormEditViewModel('test_form', 'Test', [], [\$f], [new Admin\ViewModels\Forms\FormTab('main','Main')]); \$html = Admin\Support\Forms\FormFieldRenderer::renderField(\$f, \$vm); echo 'HTML_HAS_INPUT='.(strpos(\$html,' Output zawiera: `OK_FACTORY`, `OK_RENDERER`, `OK_HANDLER`, oraz 4× `OK` dla zero-regression check `php -l` zielony × 3 dla nowych plików `git diff --stat autoload/admin/controls/ autoload/admin/factory/ autoload/admin/view/` → 0 zmian AC-2, AC-4, AC-5 satisfied (factory działa, renderer produkuje HTML, zero regresji). ## DO NOT CHANGE - `autoload/admin/controls/`, `factory/`, `view/` (legacy 50+ plików — boundaries Phase 19) - `autoload/admin/ViewModels/`, `autoload/admin/Validation/` (utworzone w 06-01, stabilne) - `autoload/Domain/**`, `autoload/Shared/**` (Phase 1-5 closed) - `autoload/autoloader.php` - `composer.json` — TYLKO regen `vendor/composer/autoload_*` przez dump-autoload, BEZ edycji `composer.json` (pre 06-01 ustawienia są wystarczające) - `composer.lock` — bez `composer update` - shopPRO source files — read-only ## SCOPE LIMITS - BaseController — explicit out-of-scope (decyzja: POJOs, brak BaseController) - `Admin\App.php` (logowanie, special_actions, routing) → Phase 12 (Users + 2FA) - Migracja istniejących admin\controls\* na nowe wzorce — Phase 7+ - Templates HTML wykorzystujące FormFieldRenderer — Phase 7+ (renderer już produkuje HTML, ale integracja z templates_admin/ to później) - Testy PHPUnit dla nowych klas — Phase 18 - Refaktoring 494-liniowego FormFieldRenderer (np. split na mniejsze rendery per typ) — deferred Przed declared complete: - [ ] `php -l` zielony dla 3 nowych plików - [ ] `composer dump-autoload` zwraca exit 0 bez warningów PSR-4 - [ ] Smoke test (Task 3) — wszystkie 4 sekcje (A/B/C/D) zwracają oczekiwane outputy - [ ] `grep -r "namespace admin\\\\Support" autoload/admin/Support/` → brak wyników (zero lowercase) - [ ] `git diff --stat` pokazuje TYLKO: 3 nowe pliki + regen vendor/composer/autoload_* - [ ] AC-1..AC-5 spełnione - 3 klasy Support dostępne: `Admin\Support\TableListRequestFactory`, `Admin\Support\Forms\FormRequestHandler`, `Admin\Support\Forms\FormFieldRenderer` - TableListRequestFactory poprawnie parsuje `?page=&per_page=&sort=&filter[]=` z `$_GET` - FormFieldRenderer produkuje HTML pola formularza z `FormField` ViewModelu - Zero regresji: Domain, Shared, Admin\ViewModels, Admin\Validation działają jak przed 06-02 - Phase 6 zamknięta — Phase 7+ ma cały fundament Po wykonaniu utworzyć `.paul/phases/06-admin-base/06-02-SUMMARY.md` z: - Lista skopiowanych plików (z liczbą linii) - Output 4 części smoke testu (literalnie) - Wszelkie deferred issues (np. refaktoring FormFieldRenderer 494 L) - Setup dla Phase 7 (jakie założenia: TableListRequestFactory + FormRequestHandler + FormFieldRenderer dostępne, brak BaseController — POJO pattern) - Aktualizacja `.paul/codebase/architecture.md` (deferred — robione w UNIFY)