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.
This commit is contained in:
Jérémy Munsch 2026-11-05 13:11:46 +01:00
parent 8f9bf1e861
commit 481d80c8ea
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
7 changed files with 124 additions and 55 deletions

View file

@ -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<string, array{unit: NutritionnalUnit, value: string}> $nutritionalValues
*/
public function __construct(
public string $name,

View file

@ -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<string, array{unit: NutritionnalUnit, value: string}> $nutritionalValues
*/
public function __construct(
public ProductId $id,

View file

@ -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

View file

@ -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

View file

@ -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 (

View file

@ -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<array-key,mixed> */
protected function mixedToArray(mixed $value): array
/**
* @return array<string, array{unit: NutritionnalUnit, value: string}>
*/
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;
}
}

View file

@ -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(),
);
}