3e46394be1
Auto Tag Develop / tag (push) Successful in 7s
## Contexte Ticket Lesstime : [#72](https://lesstime.malio.fr/project/6/task/491) (id 491) — ticket transversal, pas de spec dediee : la description du ticket fait foi. ## Implementation - **Defaut global de pagination** dans `config/packages/api_platform.yaml` : `items_per_page=10`, `maximum_items_per_page=50`, `client_items_per_page=true`, **`client_enabled=true`** (echappatoire `?pagination=false` pour alimenter les `<select>` cote front). - **`CategoryProvider` refondu** : retourne maintenant un `ApiPlatform\Doctrine\Orm\Paginator(Doctrine\ORM\Tools\Pagination\Paginator(...))` au lieu d'un array brut. Supporte `?pagination=false`. - **`AuditLogResource`** : override `paginationItemsPerPage=30 / max=50 / clientItemsPerPage=true` supprime, herite du global (10/50). `AuditLogProvider` (`DbalPaginator`) inchange. - **Autres ressources** (`Category`, `CategoryType`, `User`, `Role`, `Permission`, `Site`) : aucun changement de code, heritent automatiquement. - **Regle « pagination obligatoire »** documentee : `CLAUDE.md` (regle ABSOLUE n°13 + section « A NE PAS faire ») + `.claude/rules/backend.md` (nouvelle section dediee avec standard, override, selects, providers customs, garde-fou). - **Garde-fou CI** : `tests/Architecture/CollectionsArePaginatedTest` echoue si une `GetCollection` desactive la pagination sans whitelist `EXCLUDED`. ## Adaptation collaterale (non prevue au plan initial) 7 appels `GET /api/<collection>` dans les tests existants (`CategoryListTest`, `PermissionApiTest`, `RoleApiTest`) ont recu `?pagination=false` parce qu'ils asseyaient sur le contenu complet de l'array. Sans cette adaptation, le commit Task 1 cassait `PermissionApiTest::testCollectionFilterByOrphanFalse`. ## Criteres d'acceptation - [x] Toutes les collections API existantes paginees (plus aucun retour complet) - [x] `itemsPerPage` par defaut (10) + max borne (50) - [x] Tri / filtres / recherche fonctionnent combines a la pagination - [x] `hydra:totalItems` (cle `totalItems` en JSON-LD API Platform 4) expose pour le front - [x] Regle documentee (`CLAUDE.md` + `.claude/rules/backend.md`) ## Tests - `docker exec -t php-starseed-fpm php -d memory_limit=512M vendor/bin/phpunit` → **Tests: 320, 0 failures** (etait 312 avant ce ticket → +8 nouveaux : 5 `CategoryPaginationTest` + 2 `AuditLogPaginationRegressionTest` + 1 `CollectionsArePaginatedTest`) - `make php-cs-fixer-allow-risky` → 0 fix - Verifications HTTP manuelles : voir cahier de test dans le ticket Lesstime #72 ## Note d'incident Le tout premier commit (`9060f5d`, pose du standard YAML) a ete cree avec `--no-verify` par un subagent qui n'a pas respecte la consigne explicite « jamais de bypass de hook ». La cause sous-jacente du hook failure etait un drift BDD locale sur `ColumnsHaveSqlCommentTest`, resolu ensuite via `make db-reset`. Les 6 commits suivants ont passe le hook normalement. Le contenu de `9060f5d` est correct (15 lignes YAML ajoutees) — a re-verifier en review. ## Reviewer suggere A definir (Tristan etant l'auteur). ## Suite Debloque le volet front **ERP-73** (pagination `MalioDataTable` + composable reutilisable + cablage `?pagination=false` sur les composables de select Role/Permission/Site/CategoryType). Reviewed-on: #28 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
127 lines
4.6 KiB
PHP
127 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Architecture;
|
|
|
|
use ApiPlatform\Metadata\ApiResource;
|
|
use ApiPlatform\Metadata\GetCollection;
|
|
use PHPUnit\Framework\TestCase;
|
|
use ReflectionClass;
|
|
use Symfony\Component\Finder\Finder;
|
|
|
|
/**
|
|
* Garde-fou architecture : toute operation `GetCollection` exposee via API Platform
|
|
* doit avoir la pagination activee (ou laisser la valeur par defaut, qui est
|
|
* activee globalement dans `config/packages/api_platform.yaml`).
|
|
*
|
|
* Interdit : `new GetCollection(paginationEnabled: false)` sans exception documentee.
|
|
*
|
|
* Raison : une collection non paginee peut retourner des milliers de lignes et
|
|
* saturer la memoire du serveur, le reseau et le navigateur. La pagination est la
|
|
* seule protection fiable contre ce risque sur un CRM a donnees croissantes.
|
|
*
|
|
* Quand ajouter une entree dans `EXCLUDED` :
|
|
* - La collection est structurellement bornee (referentiel statique, < 100 items,
|
|
* jamais alimente par des utilisateurs) ET la suppression de la pagination est
|
|
* documentee avec une justification metier explicite.
|
|
* - Format obligatoire : `FQCN => 'justification + reference ticket/spec'`
|
|
*
|
|
* @internal
|
|
*/
|
|
final class CollectionsArePaginatedTest extends TestCase
|
|
{
|
|
/**
|
|
* Resources API Platform dont un `GetCollection` peut desactiver la pagination.
|
|
*
|
|
* Laisser vide au demarrage. Pour ajouter une exception :
|
|
* 'App\Module\Foo\Infrastructure\ApiPlatform\Resource\BarResource'
|
|
* => 'Referentiel statique < 50 items (types de contrat). Cf. ERP-XX.',
|
|
*
|
|
* @var array<class-string, string>
|
|
*/
|
|
private const EXCLUDED = [];
|
|
|
|
public function testAllGetCollectionOperationsHavePaginationEnabled(): void
|
|
{
|
|
$finder = new Finder()
|
|
->files()
|
|
->in(__DIR__.'/../../src')
|
|
->name('*.php')
|
|
->contains('#[ApiResource')
|
|
;
|
|
|
|
// Garde : si le scan ne trouve rien, le chemin est casse — le test
|
|
// deviendrait un faux positif vert. On verifie qu'il a du grain a moudre.
|
|
self::assertNotEmpty(
|
|
iterator_to_array($finder),
|
|
'Aucun fichier #[ApiResource] trouve sous src/ : chemin invalide ou codebase vide.',
|
|
);
|
|
|
|
foreach ($finder as $file) {
|
|
$fqcn = $this->extractFqcn($file->getRealPath());
|
|
if (null === $fqcn || !class_exists($fqcn)) {
|
|
continue;
|
|
}
|
|
|
|
$reflection = new ReflectionClass($fqcn);
|
|
$apiResourceAttributes = $reflection->getAttributes(ApiResource::class);
|
|
|
|
if ([] === $apiResourceAttributes) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($apiResourceAttributes as $attribute) {
|
|
/** @var ApiResource $apiResource */
|
|
$apiResource = $attribute->newInstance();
|
|
$operations = $apiResource->getOperations()?->getIterator() ?? [];
|
|
|
|
foreach ($operations as $operation) {
|
|
if (!$operation instanceof GetCollection) {
|
|
continue;
|
|
}
|
|
|
|
if (false !== $operation->getPaginationEnabled()) {
|
|
continue;
|
|
}
|
|
|
|
// La pagination est explicitement desactivee : verifier
|
|
// que la resource est dans la whitelist EXCLUDED.
|
|
self::assertArrayHasKey(
|
|
$fqcn,
|
|
self::EXCLUDED,
|
|
sprintf(
|
|
"La resource %s desactive la pagination sur une operation GetCollection.\n"
|
|
."Regle : toute collection API Platform doit etre paginee (cf. .claude/rules/backend.md).\n"
|
|
."Si cette collection est structurellement bornee et que la desactivation est justifiee,\n"
|
|
.'ajouter une entree dans CollectionsArePaginatedTest::EXCLUDED avec une justification.',
|
|
$fqcn,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extrait le FQCN (namespace + classe) d'un fichier PHP par lecture du
|
|
* source, sans charger le fichier.
|
|
*/
|
|
private function extractFqcn(string $path): ?string
|
|
{
|
|
$source = file_get_contents($path);
|
|
if (false === $source) {
|
|
return null;
|
|
}
|
|
|
|
if (
|
|
1 !== preg_match('/^namespace\s+([^;]+);/m', $source, $nsMatch)
|
|
|| 1 !== preg_match('/^(?:final\s+|abstract\s+|readonly\s+)*class\s+(\w+)/m', $source, $classMatch)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return trim($nsMatch[1]).'\\'.$classMatch[1];
|
|
}
|
|
}
|