Phase 6 zamknięta po 2 planach. Pełny fundament dla Phase 7-13 (migracja 17 admin controllers do Admin\ namespace). 06-01 (Forms infrastructure): - Admin\ViewModels\Forms\* — 5 ViewModeli (687 L) - Admin\Validation\FormValidator (196 L) - composer.json: php >=7.4, PSR-4 paths cross-platform safe (Admin\ → autoload/admin/, Frontend\ → autoload/front/) 06-02 (Support layer): - Admin\Support\TableListRequestFactory (99 L) — parser list z $_GET - Admin\Support\Forms\FormRequestHandler (159 L) — POST + CSRF + walidacja + persist - Admin\Support\Forms\FormFieldRenderer (494 L) — renderer HTML pól Decyzje: - Brak BaseController — Phase 7+ kontrolery jako POJOs z DI (jak shopPRO) - PSR-4 filename fix: TableListRequestFactory.php (bez shopPRO 'class.' prefix) - PascalCase namespace (Admin\Support) na lowercase folder admin/ ze względu na Windows fs case-insensitivity vs legacy admin/controls/ Pliki: 8 nowych klas, 1635 L kodu PHP 7.4-kompatybilnego, zero regresji. Smoke test: walidacja e-maila zwraca PL komunikat, factory parsuje ?page=&per_page=&sort=&filter=, Domain/Shared nadal ładują się. PHPUnit: 37/37 OK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, delegation
| phase | plan | type | wave | depends_on | files_modified | autonomous | delegation | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-admin-base | 02 | execute | 1 |
|
|
true | off |
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 → redirectFormFieldRenderer— generowanie HTML pól zFormFieldViewModeli (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.phpautoload/admin/Support/Forms/FormRequestHandler.phpautoload/admin/Support/Forms/FormFieldRenderer.php.paul/phases/06-admin-base/06-02-SUMMARY.md
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.
<acceptance_criteria>
AC-1: Wszystkie 3 klasy ładują się przez PSR-4
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
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
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ą
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 `<input` i `name="email"`
And nie wystąpi fatal error o brakującej zależności
AC-5: Zero regresji — Phase 5 i 06-01 nadal działają
Given commit przed 06-02
When `php -r "require 'vendor/autoload.php'; foreach (['Domain\\Articles\\ArticlesRepository','Shared\\Helpers\\Helpers','Admin\\ViewModels\\Forms\\FormField','Admin\\Validation\\FormValidator'] as \$c) { var_dump(class_exists(\$c)); }"`
Then 4× `bool(true)` — żaden istniejący namespace nie został rozbity
And `git diff --stat autoload/admin/controls/ autoload/admin/factory/ autoload/admin/view/` pokazuje 0 zmian (legacy nietknięte)
</acceptance_criteria>
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,'<input')!==false?'YES':'NO').PHP_EOL;
echo 'HTML_HAS_NAME='.(strpos(\$html,'name=\"email\"')!==false?'YES':'NO').PHP_EOL;
echo 'OK_RENDERER'.PHP_EOL;"
```
Oczekuję: `HTML_HAS_INPUT=YES`, `HTML_HAS_NAME=YES`, `OK_RENDERER`.
Uwaga: Jeśli `renderField` jest niestatyczna lub ma inną sygnaturę niż wyżej zakłada — dostosuj wywołanie wedle faktycznej implementacji shopPRO (NIE modyfikuj klasy; popraw smoke test).
**C) FormRequestHandler ładuje się i ma referencję do FormValidator:**
```
php -r "require 'vendor/autoload.php';
\$h = new Admin\Support\Forms\FormRequestHandler();
echo get_class(\$h).PHP_EOL;
echo 'OK_HANDLER'.PHP_EOL;"
```
Oczekuję: `Admin\Support\Forms\FormRequestHandler`, `OK_HANDLER`.
**D) Zero regresji:**
```
php -r "require 'vendor/autoload.php';
foreach (['Domain\Articles\ArticlesRepository','Shared\Helpers\Helpers','Admin\ViewModels\Forms\FormField','Admin\Validation\FormValidator'] as \$c) {
echo \$c.': '.(class_exists(\$c)?'OK':'FAIL').PHP_EOL;
}"
```
Oczekuję: 4× OK.
**Lint na nowych 3 plikach:**
`for f in autoload/admin/Support/TableListRequestFactory.php autoload/admin/Support/Forms/*.php; do php -l "$f"; done`
Avoid:
- Tworzenia trwałych plików testowych
- Modyfikacji shopPRO source files (read-only)
- Modyfikacji legacy `autoload/admin/controls/`, `factory/`, `view/`
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.phpcomposer.json— TYLKO regenvendor/composer/autoload_*przez dump-autoload, BEZ edycjicomposer.json(pre 06-01 ustawienia są wystarczające)composer.lock— bezcomposer 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
<success_criteria>
- 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
FormFieldViewModelu - Zero regresji: Domain, Shared, Admin\ViewModels, Admin\Validation działają jak przed 06-02
- Phase 6 zamknięta — Phase 7+ ma cały fundament </success_criteria>