From 331b077d79e1999c05ae35845c26dfe46e36ba2e Mon Sep 17 00:00:00 2001 From: Thomas Zilio Date: Sat, 24 Jan 2026 13:52:43 +0100 Subject: [PATCH 1/3] Update DataTables helper to use controller --- composer.json | 3 +- src/API/Controllers/BaseController.php | 56 +++++++++++++ src/API/Controllers/DataTablesController.php | 79 +++++++++++++------ src/DTO/DataTablesLoadRequest/Column.php | 13 --- .../DataTablesLoadRequest.php | 36 +++++++-- src/DTO/DataTablesLoadRequest/OrderItem.php | 9 --- src/DTO/DataTablesLoadRequest/Search.php | 8 -- .../DataTablesLoadResponse.php | 2 +- 8 files changed, 142 insertions(+), 64 deletions(-) create mode 100644 src/API/Controllers/BaseController.php diff --git a/composer.json b/composer.json index bffffe0f4..17cd026e5 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ "ext-simplexml": "*", "ext-xsl": "*", "ext-zip": "*", - "api-platform/laravel": "^4.1", + "api-platform/laravel": "^4.2.14", + "cuyz/valinor": "^2.3", "davidepastore/codice-fiscale": "^0.10.0", "devcode-it/ical-easy-reader": "dev-main", "devcode-it/sdd_ita": "dev-master", diff --git a/src/API/Controllers/BaseController.php b/src/API/Controllers/BaseController.php new file mode 100644 index 000000000..3d6bc3fda --- /dev/null +++ b/src/API/Controllers/BaseController.php @@ -0,0 +1,56 @@ +messages(); + + $formatted = []; + foreach ($messages as $message) { + $formatted[] = str_replace(". for", " for", $message->withBody('{original_message} for parameter "{node_path}"')->toString()); + } + + parent::__construct("Invalid input: ".implode("\n", $formatted)); + } +} + +abstract class BaseController extends Controller +{ + /** + * @template T + * @param class-string $class_reference + * @return T + */ + public function _cast(Request $request, string $class_reference): mixed + { + try { + return (new \CuyZ\Valinor\MapperBuilder()) + ->allowUndefinedValues() + ->allowSuperfluousKeys() + ->allowScalarValueCasting() + ->mapper() + ->map( + $class_reference, + [...$request->route()->parameters(), ...$request->all()] + ); + } catch (\CuyZ\Valinor\Mapper\MappingError $error) { + throw new InvalidInputException($error); + } + } +} diff --git a/src/API/Controllers/DataTablesController.php b/src/API/Controllers/DataTablesController.php index 7e4beec4a..fcae21183 100644 --- a/src/API/Controllers/DataTablesController.php +++ b/src/API/Controllers/DataTablesController.php @@ -2,18 +2,20 @@ namespace API\Controllers; -use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Post; -use ApiPlatform\State\ProcessorInterface; use DTO\DataTablesLoadRequest\Column; use DTO\DataTablesLoadRequest\DataTablesLoadRequest; use DTO\DataTablesLoadResponse\DataTablesLoadResponse; use Models\Module; use Util\Query; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; + + #[Post( uriTemplate: '/datatables/list/{id_module}/{id_plugin}/{id_parent}', - processor: DataTablesController::class, + controller: DataTablesController::class, input: DataTablesLoadRequest::class, output: DataTablesLoadResponse::class, )] @@ -21,11 +23,34 @@ class DataTablesResource { } -final class DataTablesController implements ProcessorInterface +final class DataTablesController extends BaseController { - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): DataTablesLoadResponse + public function __invoke(Request $request): JsonResponse { - if (!$data instanceof DataTablesLoadRequest) { + $request_body = $this->_cast($request, DataTablesLoadRequest::class); + + $id_module = (int) $request_body->getIdModule(); + $id_plugin = (int) $request_body->getIdPlugin(); + $id_parent = (int) $request_body->getIdParent(); + + $module = \Modules::get($id_module); + \Modules::setCurrent($id_module); + + $plugin = null; + if (!empty($id_plugin)) { + \Plugins::setCurrent($id_plugin); + $plugin = \Plugins::get($id_plugin); + } + + $structure = $plugin ?? $module; + + return new JsonResponse($this->retrieveRecords($structure, $request_body, $id_module, $id_plugin, $id_parent)); + } + + /* + public function process(mixed $request, Operation $operation, array $uriVariables = [], array $context = []): DataTablesLoadResponse + { + if (!$request instanceof DataTablesLoadRequest) { throw new \InvalidArgumentException(); } @@ -44,21 +69,23 @@ public function process(mixed $data, Operation $operation, array $uriVariables = $structure = $plugin ?? $module; - return $this->retrieveRecords($structure, $data, $id_module, $id_plugin, $id_parent); - } + return $this->retrieveRecords($structure, $request, $id_module, $id_plugin, $id_parent); + }*/ - private function retrieveRecords($structure, DataTablesLoadRequest $data, $id_module, $id_plugin, $id_parent): DataTablesLoadResponse + private function retrieveRecords($structure, DataTablesLoadRequest $request, $id_module, $id_plugin, $id_parent): DataTablesLoadResponse { // Informazioni fondamentali - $order = $data->order ? $data->order[0] : []; + $order = $request->order ? $request->order[0] : []; if (!empty($order)) { - $order['column'] = $order['column'] - 1; + $order->column = $order->column - 1; } + $order_array = $order->toArray(); + $query_structure = Query::readQuery($structure); - $response = new DataTablesLoadResponse($data->draw); + $response = new DataTablesLoadResponse($request->draw); $query = Query::getQuery($structure, [], [], [], $query_structure); if (empty($query)) { @@ -67,7 +94,7 @@ private function retrieveRecords($structure, DataTablesLoadRequest $data, $id_mo // Ricerca $search = []; - $columns = $data->columns; + $columns = $request->columns; array_shift($columns); for ($i = 0; $i < count($columns); ++$i) { $col = Column::fromArray($columns[$i]); @@ -80,7 +107,7 @@ private function retrieveRecords($structure, DataTablesLoadRequest $data, $id_mo $response->recordsTotal = database()->fetchNum($query); // CONTEGGIO RECORD FILTRATI (senza LIMIT) - $query_filtered = Query::getQuery($structure, $search, $order, [], $query_structure); + $query_filtered = Query::getQuery($structure, $search, $order_array, [], $query_structure); if (empty($id_plugin)) { $query_filtered = \Modules::replaceAdditionals($id_module, $query_filtered); } @@ -93,11 +120,11 @@ private function retrieveRecords($structure, DataTablesLoadRequest $data, $id_mo $response->avg = Query::getAverages($structure, $search); $limit = [ - 'start' => $data->start, - 'length' => $data->length, + 'start' => $request->start, + 'length' => $request->length, ]; // RISULTATI VISIBILI (con LIMIT) - $query = Query::getQuery($structure, $search, $order, $limit, $query_structure); + $query = Query::getQuery($structure, $search, $order_array, $limit, $query_structure); // Filtri derivanti dai permessi (eventuali) if (empty($id_plugin)) { @@ -154,7 +181,7 @@ private function getSingleRow($r, $query_structure, $align, $id_module, $id_plug $result = [ 'id' => $r['id'], - '', // Colonna ID + '', // Colonna ID ]; foreach ($query_structure['fields'] as $pos => $field) { @@ -162,9 +189,9 @@ private function getSingleRow($r, $query_structure, $align, $id_module, $id_plug if (!empty($r['_bg_'])) { if (preg_match('/-light$/', (string) $r['_bg_'])) { - $column['data-background'] = substr((string) $r['_bg_'], 0, -6); // Remove the "-light" suffix from the word + $column['request-background'] = substr((string) $r['_bg_'], 0, -6); // Remove the "-light" suffix from the word } else { - $column['data-background'] = $r['_bg_']; + $column['request-background'] = $r['_bg_']; } } @@ -211,7 +238,7 @@ private function getSingleRow($r, $query_structure, $align, $id_module, $id_plug } $column['class'] = 'text-center small'; - $column['data-background'] = $r[$field]; + $column['request-background'] = $r[$field]; } // Icona di stampa @@ -245,8 +272,8 @@ private function getSingleRow($r, $query_structure, $align, $id_module, $id_plug } // Colore del testo - if (!empty($column['data-background'])) { - $column['data-color'] ??= color_inverse(trim((string) $column['data-background'])); + if (!empty($column['request-background'])) { + $column['request-color'] ??= color_inverse(trim((string) $column['request-background'])); } elseif (preg_match('/^mailto_(.+?)$/', trim((string) $field), $m)) { $column['class'] = ''; $value = ($r[$field] ? ' '.$r[$field].'' : ''); @@ -269,13 +296,13 @@ private function getSingleRow($r, $query_structure, $align, $id_module, $id_plug // Link per i moduli if (empty($id_plugin)) { - $column['data-link'] = base_path_osm().'/editor.php?id_module='.$id_module.'&id_record='.$id_record.$hash; + $column['request-link'] = base_path_osm().'/editor.php?id_module='.$id_module.'&id_record='.$id_record.$hash; } // Link per i plugin else { - $column['data-link'] = base_path_osm().'/add.php?id_module='.$id_module.'&id_record='.$id_record.'&id_plugin='.$id_plugin.'&id_parent='.$id_parent.'&edit=1'.$hash; + $column['request-link'] = base_path_osm().'/add.php?id_module='.$id_module.'&id_record='.$id_record.'&id_plugin='.$id_plugin.'&id_parent='.$id_parent.'&edit=1'.$hash; - $column['data-type'] = 'dialog'; + $column['request-type'] = 'dialog'; } } diff --git a/src/DTO/DataTablesLoadRequest/Column.php b/src/DTO/DataTablesLoadRequest/Column.php index 37d6a9113..756e4f419 100644 --- a/src/DTO/DataTablesLoadRequest/Column.php +++ b/src/DTO/DataTablesLoadRequest/Column.php @@ -15,19 +15,6 @@ public function __construct( ) { } - public static function fromArray(array $input = []): self - { - $data = isset($input['data']) ? (string) $input['data'] : ''; - $name = isset($input['name']) ? (string) $input['name'] : null; - $searchable = isset($input['searchable']) ? filter_var($input['searchable'], FILTER_VALIDATE_BOOLEAN) : true; - $orderable = isset($input['orderable']) ? filter_var($input['orderable'], FILTER_VALIDATE_BOOLEAN) : true; - $search = isset($input['search']) && is_array($input['search']) - ? Search::fromArray($input['search']) - : null; - - return new self($data, $name, $searchable, $orderable, $search); - } - public function getData(): string { return $this->data; diff --git a/src/DTO/DataTablesLoadRequest/DataTablesLoadRequest.php b/src/DTO/DataTablesLoadRequest/DataTablesLoadRequest.php index 54dc7a6b2..b67acbe2f 100644 --- a/src/DTO/DataTablesLoadRequest/DataTablesLoadRequest.php +++ b/src/DTO/DataTablesLoadRequest/DataTablesLoadRequest.php @@ -4,25 +4,49 @@ namespace DTO\DataTablesLoadRequest; +use Symfony\Component\Serializer\Attribute\Ignore; + final class DataTablesLoadRequest { + // Private properties are from the route + // We must use Ignore on the getters to prevent them from being serialized + private ?int $id_module = 0; + private ?int $id_plugin = 0; + private ?int $id_parent = 0; + public int $draw = 0; public int $start = 0; public int $length = 200; - /** - * @var Search - */ - public array $search; // Deserialization is not working correctly + + public Search $search; /** * @var OrderItem[] */ - public array $order = []; // Deserialization is not working correctly + public array $order = []; /** * @var Column[] */ - public array $columns = []; // Deserialization is not working correctly + public array $columns = []; public ?string $_ = null; + #[Ignore] + public function getIdModule(): int + { + return $this->id_module; + } + + #[Ignore] + public function getIdPlugin(): int + { + return $this->id_plugin; + } + + #[Ignore] + public function getIdParent(): int + { + return $this->id_parent; + } + public function getDraw(): int { return $this->draw; diff --git a/src/DTO/DataTablesLoadRequest/OrderItem.php b/src/DTO/DataTablesLoadRequest/OrderItem.php index 0556dbfb2..b1546c2c7 100644 --- a/src/DTO/DataTablesLoadRequest/OrderItem.php +++ b/src/DTO/DataTablesLoadRequest/OrderItem.php @@ -14,15 +14,6 @@ public function __construct(public int $column = 0, public ?string $name = null, { } - public static function fromArray(array $input = []): self - { - $col = isset($input['column']) ? (int) $input['column'] : 0; - $name = isset($input['name']) && $input['name'] !== '' ? (string) $input['name'] : null; - $dirStr = isset($input['dir']) ? (string) $input['dir'] : SortDirection::ASC->value; - - return new self($col, $name, SortDirection::from($dirStr)); - } - public function getColumnIndex(): int { return $this->column; diff --git a/src/DTO/DataTablesLoadRequest/Search.php b/src/DTO/DataTablesLoadRequest/Search.php index 4b1cbc603..9e841d4dc 100644 --- a/src/DTO/DataTablesLoadRequest/Search.php +++ b/src/DTO/DataTablesLoadRequest/Search.php @@ -10,14 +10,6 @@ public function __construct(public string $value, public bool $regex = false) { } - public static function fromArray(array $input = []): self - { - $value = isset($input['value']) ? (string) $input['value'] : ''; - $regex = isset($input['regex']) ? filter_var($input['regex'], FILTER_VALIDATE_BOOLEAN) : false; - - return new self($value, $regex); - } - public function getValue(): string { return $this->value; diff --git a/src/DTO/DataTablesLoadResponse/DataTablesLoadResponse.php b/src/DTO/DataTablesLoadResponse/DataTablesLoadResponse.php index 069eb9537..ecf22daf1 100644 --- a/src/DTO/DataTablesLoadResponse/DataTablesLoadResponse.php +++ b/src/DTO/DataTablesLoadResponse/DataTablesLoadResponse.php @@ -31,7 +31,7 @@ public function toArray(): array 'data' => $this->data, 'summable' => $this->summable, 'avg' => $this->avg, - 'error' => $this->error, + $this->error ? ['error' => $this->error] : [], ]; } } From a1db8099faaec950b643e2be6f3202fd836c2b2d Mon Sep 17 00:00:00 2001 From: Thomas Zilio Date: Sat, 7 Feb 2026 09:49:10 +0100 Subject: [PATCH 2/3] Update Impostazioni to use controller system --- .../Controllers/GetImpostazioneController.php | 24 ++++++++++++++ .../Controllers/GetImpostazioneProvider.php | 23 ------------- ...der.php => ListImpostazioniController.php} | 20 +++++------ ... => ListSezioniImpostazioniController.php} | 11 ++++--- .../UpdateImpostazioneController.php | 32 ++++++++++++++++++ .../UpdateImpostazioneProcessor.php | 33 ------------------- .../src/API/ImpostazioneResource.php | 17 +++++----- 7 files changed, 80 insertions(+), 80 deletions(-) create mode 100644 modules/impostazioni/src/API/Controllers/GetImpostazioneController.php delete mode 100644 modules/impostazioni/src/API/Controllers/GetImpostazioneProvider.php rename modules/impostazioni/src/API/Controllers/{ListImpostazioniProvider.php => ListImpostazioniController.php} (53%) rename modules/impostazioni/src/API/Controllers/{ListSezioniImpostazioniProvider.php => ListSezioniImpostazioniController.php} (63%) create mode 100644 modules/impostazioni/src/API/Controllers/UpdateImpostazioneController.php delete mode 100644 modules/impostazioni/src/API/Controllers/UpdateImpostazioneProcessor.php diff --git a/modules/impostazioni/src/API/Controllers/GetImpostazioneController.php b/modules/impostazioni/src/API/Controllers/GetImpostazioneController.php new file mode 100644 index 000000000..bcaddaae3 --- /dev/null +++ b/modules/impostazioni/src/API/Controllers/GetImpostazioneController.php @@ -0,0 +1,24 @@ +route('id')); + if (!$setting) { + return null; + } + + $response = ImpostazioneResource::fromModel($setting); + + return new JsonResponse($response); + } +} diff --git a/modules/impostazioni/src/API/Controllers/GetImpostazioneProvider.php b/modules/impostazioni/src/API/Controllers/GetImpostazioneProvider.php deleted file mode 100644 index e37422074..000000000 --- a/modules/impostazioni/src/API/Controllers/GetImpostazioneProvider.php +++ /dev/null @@ -1,23 +0,0 @@ -getParameters()->get('sezione')->getValue(); - $search = $operation->getParameters()->get('ricerca')->getValue(); + $sezione = $request->query('sezione'); + $search = $request->query('ricerca'); // Trova le impostazioni che corrispondono alla ricerca - if (!$search instanceof ParameterNotFound) { + if (!empty($search)) { $impostazioni = Setting::where('nome', 'like', '%'.$search.'%') ->orWhere('sezione', 'like', '%'.$search.'%') ->get(); - } elseif (!$sezione instanceof ParameterNotFound) { + } elseif (!empty($sezione)) { $impostazioni = Setting::where('sezione', $sezione)->get(); } else { $impostazioni = Setting::all(); @@ -31,6 +31,6 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $results[] = ImpostazioneResource::fromModel($impostazione); } - return $results; + return new JsonResponse($results); } } diff --git a/modules/impostazioni/src/API/Controllers/ListSezioniImpostazioniProvider.php b/modules/impostazioni/src/API/Controllers/ListSezioniImpostazioniController.php similarity index 63% rename from modules/impostazioni/src/API/Controllers/ListSezioniImpostazioniProvider.php rename to modules/impostazioni/src/API/Controllers/ListSezioniImpostazioniController.php index cf89b5376..1877a18c3 100644 --- a/modules/impostazioni/src/API/Controllers/ListSezioniImpostazioniProvider.php +++ b/modules/impostazioni/src/API/Controllers/ListSezioniImpostazioniController.php @@ -2,14 +2,15 @@ namespace Modules\Impostazioni\API\Controllers; -use ApiPlatform\Metadata\Operation; -use ApiPlatform\State\ProviderInterface; +use API\Controllers\BaseController; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Models\Setting; use Modules\Impostazioni\API\Models\ListSezioniImpostazioniResponse; -final class ListSezioniImpostazioniProvider implements ProviderInterface +final class ListSezioniImpostazioniController extends BaseController { - public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?ListSezioniImpostazioniResponse + public function __invoke(Request $request): JsonResponse { $gruppi = Setting::selectRaw('sezione AS nome, COUNT(id) AS numero') ->groupBy(['sezione']) @@ -23,6 +24,6 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $response->sezioni[$gruppo->nome] = $gruppo->numero; } - return $response; + return new JsonResponse($response); } } diff --git a/modules/impostazioni/src/API/Controllers/UpdateImpostazioneController.php b/modules/impostazioni/src/API/Controllers/UpdateImpostazioneController.php new file mode 100644 index 000000000..d2105d9f0 --- /dev/null +++ b/modules/impostazioni/src/API/Controllers/UpdateImpostazioneController.php @@ -0,0 +1,32 @@ +_cast($request, UpdateImpostazioneRequest::class); + + $id = $request->route('id'); + $response = new UpdateImpostazioneResponse(); + + $impostazione = Setting::find($id); + if (!$impostazione->editable) { + $response->edited = true; + + return new JsonResponse($response); + } + + $response->edited = \Settings::setValue($impostazione->id, $data->valore); + + return new JsonResponse($response); + } +} diff --git a/modules/impostazioni/src/API/Controllers/UpdateImpostazioneProcessor.php b/modules/impostazioni/src/API/Controllers/UpdateImpostazioneProcessor.php deleted file mode 100644 index 0d449b762..000000000 --- a/modules/impostazioni/src/API/Controllers/UpdateImpostazioneProcessor.php +++ /dev/null @@ -1,33 +0,0 @@ -editable) { - $response->edited = true; - - return $response; - } - - $response->edited = \Settings::setValue($impostazione->id, $data->valore); - - return $response; - } -} diff --git a/modules/impostazioni/src/API/ImpostazioneResource.php b/modules/impostazioni/src/API/ImpostazioneResource.php index a52c0826f..6a8116028 100644 --- a/modules/impostazioni/src/API/ImpostazioneResource.php +++ b/modules/impostazioni/src/API/ImpostazioneResource.php @@ -8,10 +8,10 @@ use ApiPlatform\Metadata\Put; use ApiPlatform\Metadata\QueryParameter; use Models\Setting; -use Modules\Impostazioni\API\Controllers\GetImpostazioneProvider; -use Modules\Impostazioni\API\Controllers\ListImpostazioniProvider; -use Modules\Impostazioni\API\Controllers\ListSezioniImpostazioniProvider; -use Modules\Impostazioni\API\Controllers\UpdateImpostazioneProcessor; +use Modules\Impostazioni\API\Controllers\GetImpostazioneController; +use Modules\Impostazioni\API\Controllers\ListImpostazioniController; +use Modules\Impostazioni\API\Controllers\ListSezioniImpostazioniController; +use Modules\Impostazioni\API\Controllers\UpdateImpostazioneController; use Modules\Impostazioni\API\Models\ListSezioniImpostazioniResponse; use Modules\Impostazioni\API\Models\UpdateImpostazioneRequest; use Modules\Impostazioni\API\Models\UpdateImpostazioneResponse; @@ -21,12 +21,12 @@ operations: [ new Get( uriTemplate: '/impostazioni/sezioni', - provider: ListSezioniImpostazioniProvider::class, + controller: ListSezioniImpostazioniController::class, output: ListSezioniImpostazioniResponse::class, ), new GetCollection( uriTemplate: '/impostazioni', - provider: ListImpostazioniProvider::class, + controller: ListImpostazioniController::class, paginationEnabled: false, parameters: [ 'ricerca' => new QueryParameter(property: 'hydra:freetextQuery', required: false), @@ -35,12 +35,11 @@ ), new Get( uriTemplate: '/impostazione/{id}', - provider: GetImpostazioneProvider::class, + controller: GetImpostazioneController::class, ), new Put( uriTemplate: '/impostazione/{id}', - provider: GetImpostazioneProvider::class, - processor: UpdateImpostazioneProcessor::class, + controller: UpdateImpostazioneController::class, input: UpdateImpostazioneRequest::class, output: UpdateImpostazioneResponse::class, ), From e04a9d030cd0eadc99e50ddfb7da1b8f25e56291 Mon Sep 17 00:00:00 2001 From: Thomas Zilio Date: Sat, 7 Feb 2026 09:50:10 +0100 Subject: [PATCH 3/3] Disable provider/processor auto-discovery --- src/Providers/AppServiceProvider.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Providers/AppServiceProvider.php b/src/Providers/AppServiceProvider.php index cb3a8a48a..02d397b60 100644 --- a/src/Providers/AppServiceProvider.php +++ b/src/Providers/AppServiceProvider.php @@ -39,6 +39,8 @@ public function boot(): void $translator->setLocale($lang, $formatter); } + /* + Disable: we should use controllers for better performance // Register all Providers and Processors from Modules and Plugins foreach (get_declared_classes() as $className) { if (str_contains($className, 'Modules\\') || str_contains($className, 'API\\') || str_contains($className, 'Plugins\\')) { @@ -50,5 +52,6 @@ public function boot(): void } } } + */ } }