ldc-ovoteam/app/Directories/EloquentProductDirectory.php
Jérémy Munsch e55889369b
refactor(requests): simplify file validation rules
Updated the UpdateProductRequest and UpdateRecipeRequest to use built-in validation rules for file uploads, removing custom validation methods for pictures and PDFs. This change streamlines the validation process and improves code readability.

refactor(helper): remove unused FileStorageHelper

Deleted the FileStorageHelper class as it was no longer needed, simplifying the codebase.

refactor(toolbox): clean up ToolboxHelper methods

Removed unused methods and simplified existing ones in ToolboxHelper to enhance maintainability.

feat(models): add translation search method to Product and Recipe

Added a new method `whereTranslationLike` to both Product and Recipe models to facilitate searching translations.

chore(config): add storage service prefix configuration

Introduced a new configuration option for storage service prefix to allow customization of media paths.

fix(validation): add file existence validation message

Added a French translation for the file existence validation message to improve user feedback.

feat(routes): add media route for file access

Implemented a new route for accessing media files through a MediaController, enhancing file management capabilities.
2026-11-04 10:18:51 +01:00

224 lines
7.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Directories;
use App\Contracts\Directories\ProductDirectory;
use App\Dtos\CategoryProducts;
use App\Dtos\ProductSummary;
use App\Models\Product;
use App\Models\Recipe;
use Domain\Models\Entities\Product as DomainProduct;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\Entities\RecipeId;
use Domain\Models\ValueObjects\Pagination;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
class EloquentProductDirectory implements ProductDirectory
{
public function find(ProductId $id): ?DomainProduct
{
return Product::query()
->with(['recipes'])
->where('id', (string) $id->value)
->limit(1)
->get()
->map(fn (Product $product) => $this->mapToEntity($product))
->first();
}
public function findBySlug(string $slug): ?DomainProduct
{
return Product::query()
->with(['recipes'])
->where('slug', $slug)
->limit(1)
->get()
->map(fn (Product $product) => $this->mapToEntity($product))
->first();
}
public function getAllProductsSummary(): array
{
return Product::query()
->select(['id', 'name', 'picture'])
->orderBy('name', 'asc')
->get()
->map(fn (Product $product) => $this->mapToSummary($product))
->all();
}
public function getPaginatedProducts(
Pagination $pagination,
string $search = '',
): LengthAwarePaginator {
$dbQueryCallback = function (Builder $query) {
/** @var \Illuminate\Database\Eloquent\Builder<Product> $query */
$query->with(['recipes']);
};
if (! empty($search)) {
$scout = Product::search($search)->query($dbQueryCallback);
/** @var \Illuminate\Pagination\LengthAwarePaginator<array-key,Product> $paginated */
$paginated = $scout->paginate(
$pagination->pageSize(),
'page',
$pagination->pageNumber()
);
return $paginated->through(fn ($product) => $this->mapToEntity($product));
}
/** @var \Illuminate\Database\Eloquent\Builder<Product> $query */
$query = Product::query();
$dbQueryCallback($query);
return $query->paginate(
perPage: $pagination->pageSize(),
page: $pagination->pageNumber()
)->through(fn (Product $product) => $this->mapToEntity($product));
}
/**
* @param \Domain\Models\ValueObjects\Products\BreedingMethod[] $breedingMethods
* @param \Domain\Models\ValueObjects\Products\Technology[] $technologies
* @param \Domain\Models\ValueObjects\Products\Business[] $businesses
*/
public function getCategoryProducts(
ProductCategory $category,
array $breedingMethods = [],
array $technologies = [],
array $businesses = [],
?bool $isEgalim = null,
string $search = '',
int $limitPerCategory = 6
): ?CategoryProducts {
$dbQueryCallback = function (Builder $query) use ($category, $breedingMethods, $technologies, $businesses, $isEgalim) {
/** @var \Illuminate\Database\Eloquent\Builder<Product> $query */
$query->where('category', $category->value)
->with(['recipes']);
if (! empty($breedingMethods)) {
$query->where(function ($q) use ($breedingMethods) {
foreach ($breedingMethods as $method) {
$q->orWhereJsonContains('breeding_methods', $method->value);
}
});
}
if (! empty($technologies)) {
$techValues = array_map(fn ($t) => $t->value, $technologies);
$query->whereIn('technology', $techValues);
}
if (! empty($businesses)) {
$query->where(function ($q) use ($businesses) {
foreach ($businesses as $business) {
$q->orWhereJsonContains('businesses', $business->value);
}
});
}
if ($isEgalim === true) {
$query->where('is_egalim', true);
}
};
$query = Product::query();
if (! empty($search)) {
$scout = Product::search($search);
// Get all matching IDs from Meilisearch
$matchingIds = $scout->keys();
if ($matchingIds->isEmpty()) {
return null;
}
$query->whereIn('id', $matchingIds);
}
$dbQueryCallback($query);
$count = $query->count();
if ($count === 0) {
return null;
}
$products = (clone $query)->limit($limitPerCategory)
->get()
->map(fn (Product $product) => $this->mapToEntity($product))
->all();
return new CategoryProducts(
category: $category,
total: $count,
products: $products,
);
}
private function mapToSummary(Product $product): ProductSummary
{
return new ProductSummary(
id: (string) $product->id,
name: $product->name,
picture: $product->picture?->value,
);
}
private function mapToEntity(Product $product): DomainProduct
{
return new DomainProduct(
id: new ProductId(Uuid::fromString($product->id)),
name: $product->name,
slug: $product->slug,
category: $product->category,
technology: $product->technology,
isEgalim: $product->is_egalim,
breedingMethods: $product->breeding_methods->all(),
businesses: $product->businesses->all(),
detailsAndAdvantages: $product->details_and_advantages,
formats: $product->formats->all(),
handlingAdvices: $product->handling_advices,
ingredients: $product->ingredients,
conservationBeforeOpening: $product->conservation_before_opening,
description: $product->description,
seoTitle: $product->seo_title,
seoDescription: $product->seo_description,
conservationAfterOpening: $product->conservation_after_opening,
nutritionalValues: $product->nutritional_values,
picture: $product->picture->value,
pictureAlt: $product->picture_alt,
pictureTitle: $product->picture_title,
pdfFilePath: $product->pdf?->value,
associatedRecipes: $product->recipes->map(
fn (Recipe $recipe): UuidInterface => $recipe->getId()->value
)->all()
);
}
public function getRelatedProducts(RecipeId $recipeId, int $limit = 4): array
{
$recipe = Recipe::query()
->where('id', $recipeId->value)
->first();
if (! $recipe) {
return [];
}
return $recipe->products()
->with(['recipes'])
->limit($limit)
->get()
->map(fn (Product $product) => $this->mapToEntity($product))
->all();
}
}