From 481d80c8ea7b2dac93f08a9dd4cda2d663f6cbc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 5 Nov 2026 13:11:46 +0100 Subject: [PATCH] refactor(requests): simplify nutritional values handling in product requests Updated the handling of nutritional values in StoreProductRequest and UpdateProductRequest to improve clarity and maintainability. The logic for processing nutritional values has been centralized into a dedicated method, enhancing code readability. --- .../Products/Create/CreateProductInput.php | 2 +- .../Products/Update/UpdateProductInput.php | 2 +- .../Controllers/Back/ProductController.php | 19 +-- .../Controllers/Back/RecipeController.php | 19 +-- app/Http/Middlewares/SecureHeaders.php | 8 +- .../Back/Products/StoreProductRequest.php | 115 +++++++++++++++--- .../Back/Products/UpdateProductRequest.php | 14 +-- 7 files changed, 124 insertions(+), 55 deletions(-) diff --git a/app/Cqs/Actions/Products/Create/CreateProductInput.php b/app/Cqs/Actions/Products/Create/CreateProductInput.php index e2b59f19..399a9b51 100644 --- a/app/Cqs/Actions/Products/Create/CreateProductInput.php +++ b/app/Cqs/Actions/Products/Create/CreateProductInput.php @@ -20,7 +20,7 @@ final readonly class CreateProductInput implements ActionInput * @param BreedingMethod[] $breedingMethods * @param Business[] $businesses * @param ProductFormat[] $formats - * @param array{unit: NutritionnalUnit, value: string}[] $nutritionalValues + * @param array $nutritionalValues */ public function __construct( public string $name, diff --git a/app/Cqs/Actions/Products/Update/UpdateProductInput.php b/app/Cqs/Actions/Products/Update/UpdateProductInput.php index 34747a36..f7c68968 100644 --- a/app/Cqs/Actions/Products/Update/UpdateProductInput.php +++ b/app/Cqs/Actions/Products/Update/UpdateProductInput.php @@ -21,7 +21,7 @@ final readonly class UpdateProductInput implements ActionInput * @param BreedingMethod[] $breedingMethods * @param Business[] $businesses * @param ProductFormat[] $formats - * @param array{unit: NutritionnalUnit, value: string}[] $nutritionalValues + * @param array $nutritionalValues */ public function __construct( public ProductId $id, diff --git a/app/Http/Controllers/Back/ProductController.php b/app/Http/Controllers/Back/ProductController.php index 67170fc7..1c254e61 100644 --- a/app/Http/Controllers/Back/ProductController.php +++ b/app/Http/Controllers/Back/ProductController.php @@ -123,18 +123,21 @@ class ProductController extends BackendController UpdateProductRequest $request, Product $product, UpdateProduct $updateProduct, - ): ProductDto { + ): RedirectResponse { $this->authorize('update', $product); $result = $this->executor->execute($updateProduct, $request->toUpdateProductInput($product)); - if (! $result instanceof ProductDto) { - abort(500, trans('uwil-backend::crud.messages.cannot_be_updated', [ - 'model' => Str::ucfirst(trans_choice('models.classes.product', 1)), - ])); - } - - return $result; + return match (true) { + $result instanceof ProductDto => redirect()->route('back.products.edit', $product) + ->with('success', trans('uwil-backend::crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.product', 1)), + ])), + default => redirect()->route('back.products.edit', $product) + ->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.product', 1)), + ])), + }; } public function duplicate(Product $product): Response diff --git a/app/Http/Controllers/Back/RecipeController.php b/app/Http/Controllers/Back/RecipeController.php index fe1bd7c6..5047ea64 100644 --- a/app/Http/Controllers/Back/RecipeController.php +++ b/app/Http/Controllers/Back/RecipeController.php @@ -128,18 +128,21 @@ class RecipeController extends BackendController UpdateRecipeRequest $request, Recipe $recipe, UpdateRecipe $updateRecipe, - ): RecipeDto { + ): RedirectResponse { $this->authorize('update', $recipe); $result = $this->executor->execute($updateRecipe, $request->toUpdateRecipeInput($recipe)); - if (! $result instanceof RecipeDto) { - abort(500, trans('uwil-backend::crud.messages.cannot_be_updated', [ - 'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)), - ])); - } - - return $result; + return match (true) { + $result instanceof RecipeDto => redirect()->route('back.recipes.edit', $recipe) + ->with('success', trans('uwil-backend::crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)), + ])), + default => redirect()->route('back.recipes.edit', $recipe) + ->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)), + ])), + }; } public function duplicate(Recipe $recipe, GetProductsSummariesQuery $getProductsSummaries): Response diff --git a/app/Http/Middlewares/SecureHeaders.php b/app/Http/Middlewares/SecureHeaders.php index dbee66da..dda21ae0 100644 --- a/app/Http/Middlewares/SecureHeaders.php +++ b/app/Http/Middlewares/SecureHeaders.php @@ -4,12 +4,11 @@ declare(strict_types=1); namespace App\Http\Middlewares; -use App\Lib\PublicS3Disk; -use Closure; use Illuminate\Contracts\Container\Container; use Illuminate\Http\Request; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Vite; +use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\Response; class SecureHeaders @@ -36,7 +35,7 @@ class SecureHeaders * * @param \Closure(\Illuminate\Http\Request):(\Illuminate\Http\Response) $next */ - public function handle(Request $request, Closure $next): Response + public function handle(Request $request, \Closure $next): Response { $appUrl = Config::string('app.url'); $appDebug = Config::boolean('app.debug'); @@ -67,7 +66,8 @@ class SecureHeaders $response->headers->set('X-Frame-Options', 'SAMEORIGIN'); $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); - $s3PublicEndpoint = $this->container->make(PublicS3Disk::class)->urlEndpoint; + $s3PublicEndpoint = Str::of(Config::string('filesystems.disks.s3.url')) + ->chopEnd('/')->append('/')->value(); // * Ignore CSP on some conditions. if ( diff --git a/app/Http/Requests/Back/Products/StoreProductRequest.php b/app/Http/Requests/Back/Products/StoreProductRequest.php index 0a25199c..f5ecf591 100644 --- a/app/Http/Requests/Back/Products/StoreProductRequest.php +++ b/app/Http/Requests/Back/Products/StoreProductRequest.php @@ -47,6 +47,20 @@ class StoreProductRequest extends FormRequest public function toCreateProductInput(): CreateProductInput { + $picture = null; + if ($this->hasFile('picture')) { + $picture = $this->storeFile($this->file('picture')); + } elseif ($this->filled('picture')) { + $picture = new StorageFilePath($this->string('picture')->value()); + } + + $pdf = null; + if ($this->hasFile('pdf')) { + $pdf = $this->storeFile($this->file('pdf')); + } elseif ($this->filled('pdf')) { + $pdf = new StorageFilePath($this->string('pdf')->value()); + } + return new CreateProductInput( name: $this->string('name')->trim()->value(), seoTitle: $this->string('seo_title')->trim()->value(), @@ -79,21 +93,11 @@ class StoreProductRequest extends FormRequest conservationBeforeOpening: $this->string('conservation_before_opening')->trim()->value(), conservationAfterOpening: $this->filled('conservation_after_opening') ? $this->string('conservation_after_opening')->trim()->value() : null, - picture: $this->hasFile('picture') ? $this->storeFile($this->file('picture')) : null, + picture: $picture, pictureAlt: $this->string('picture_alt')->trim()->value(), pictureTitle: $this->string('picture_title')->trim()->value(), - pdf: $this->hasFile('pdf') ? $this->storeFile($this->file('pdf')) : null, - nutritionalValues: array_map( - function ($value) { - $value = $this->mixedToArray($value); - - return [ - 'unit' => NutritionnalUnit::from(Arr::string($value, 'unit')), - 'value' => Arr::string($value, 'value'), - ]; - }, - $this->array('nutritional_values') - ), + pdf: $pdf, + nutritionalValues: $this->getNutritionalValues(), ); } @@ -102,7 +106,7 @@ class StoreProductRequest extends FormRequest */ protected function prepareForValidation(): void { - $this->merge([ + $merges = [ 'name' => $this->string('name')->trim()->value(), 'seo_title' => $this->string('seo_title')->trim()->value(), 'seo_description' => $this->string('seo_description')->trim()->value(), @@ -110,7 +114,27 @@ class StoreProductRequest extends FormRequest 'picture_title' => $this->string('picture_title')->trim()->value(), 'conservation_before_opening' => $this->string('conservation_before_opening')->trim()->value(), 'conservation_after_opening' => $this->string('conservation_after_opening')->trim()->value() ?: null, - ]); + ]; + + if ($this->has('nutritional_values') && is_array($this->input('nutritional_values'))) { + $nutritionalValues = $this->array('nutritional_values'); + foreach ($nutritionalValues as $key => $item) { + $value = (is_array($item) && isset($item['value']) && is_numeric($item['value'])) ? + $this->mixedToString($item['value']) : null; + + if ($value !== null) { + $nutValue = when( + is_array($item) && isset($item['value']) && is_numeric($item['value']), + fn () => $this->mixedToString($item['value']), + fn () => null + ); + $nutritionalValues[$key] = $nutValue; + } + } + $merges['nutritional_values'] = $nutritionalValues; + } + + $this->merge($merges); } /** @@ -225,13 +249,64 @@ class StoreProductRequest extends FormRequest return (string) $value; } - /** @return array */ - protected function mixedToArray(mixed $value): array + /** + * @return array + */ + public function getNutritionalValues(): array { - if (! is_array($value)) { - throw new \InvalidArgumentException('Value must be an array to convert to array.'); + $raw = $this->input('nutritional_values'); + + if (! is_array($raw) || $raw === []) { + return []; } - return $value; + $result = []; + + foreach ($raw as $key => $item) { + if (! is_array($item)) { + continue; + } + + $rawUnit = Arr::get($item, 'unit'); + $rawValue = Arr::get($item, 'value'); + + if ($rawUnit === null || $rawValue === null) { + continue; + } + + $unit = $rawUnit instanceof NutritionnalUnit + ? $rawUnit + : NutritionnalUnit::tryFrom($this->mixedToString($rawUnit)); + + $value = mb_trim($this->mixedToString($rawValue)); + + if ($unit === null || $value === '') { + continue; + } + + $result[(string) $key] = [ + 'unit' => $unit, + 'value' => $value, + ]; + } + + $nutrients = array_filter( + $result, + fn (string $k) => $k !== 'refWeightG', + ARRAY_FILTER_USE_KEY + ); + + if (empty($nutrients)) { + return []; + } + + if (! isset($result['refWeightG'])) { + $result['refWeightG'] = [ + 'unit' => NutritionnalUnit::GRAM, + 'value' => '100', + ]; + } + + return $result; } } diff --git a/app/Http/Requests/Back/Products/UpdateProductRequest.php b/app/Http/Requests/Back/Products/UpdateProductRequest.php index 29f83b01..fbf31784 100644 --- a/app/Http/Requests/Back/Products/UpdateProductRequest.php +++ b/app/Http/Requests/Back/Products/UpdateProductRequest.php @@ -8,11 +8,9 @@ use App\Cqs\Actions\Products\Update\UpdateProductInput; use App\Models\Product; use Domain\Models\ValueObjects\Products\BreedingMethod; use Domain\Models\ValueObjects\Products\Business; -use Domain\Models\ValueObjects\Products\NutritionnalUnit; use Domain\Models\ValueObjects\Products\ProductCategory; use Domain\Models\ValueObjects\Products\ProductFormat; use Domain\Models\ValueObjects\Products\Technology; -use Illuminate\Support\Arr; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; use Uwil\Storage\Lib\ValueObjects\StorageFilePath; @@ -81,17 +79,7 @@ class UpdateProductRequest extends StoreProductRequest pictureAlt: $this->string('picture_alt')->trim()->value(), pictureTitle: $this->string('picture_title')->trim()->value(), pdf: $pdf, - nutritionalValues: array_map( - function ($value) { - $value = $this->mixedToArray($value); - - return [ - 'unit' => NutritionnalUnit::from(Arr::string($value, 'unit')), - 'value' => Arr::string($value, 'value'), - ]; - }, - $this->array('nutritional_values') - ), + nutritionalValues: $this->getNutritionalValues(), ); }