issues/27: filter DTO, strip id from payloads, lifecycle updateAt

This commit is contained in:
Valery Petrov
2026-05-15 15:35:50 +03:00
committed by Valeriy Petrov
parent da5f7bb242
commit 76044381fd
22 changed files with 153 additions and 129 deletions
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Controller; namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Article; use App\Entity\Article;
use App\Repository\ArticleRepository; use App\Repository\ArticleRepository;
use App\Service\Crud\CrudResponder; use App\Service\Crud\CrudResponder;
@@ -37,7 +38,7 @@ final class ArticleController extends AbstractController
#[Route('/list', name: 'article_list', methods: ['GET'])] #[Route('/list', name: 'article_list', methods: ['GET'])]
public function list(Request $request, ArticleRepository $repository): JsonResponse public function list(Request $request, ArticleRepository $repository): JsonResponse
{ {
$qb = $repository->createFilteredQueryBuilder($request->query->all()); $qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request));
return $this->json($this->paginator->paginateWithLegacyMeta($qb, $request), Response::HTTP_OK, [], [ return $this->json($this->paginator->paginateWithLegacyMeta($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS, 'groups' => self::READ_GROUPS,
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Controller; namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Disease; use App\Entity\Disease;
use App\Repository\DiseaseRepository; use App\Repository\DiseaseRepository;
use App\Service\Crud\CrudResponder; use App\Service\Crud\CrudResponder;
@@ -36,7 +37,7 @@ final class DiseaseController extends AbstractController
#[Route('/list', name: 'disease_list', methods: ['GET'])] #[Route('/list', name: 'disease_list', methods: ['GET'])]
public function list(Request $request, DiseaseRepository $repository): JsonResponse public function list(Request $request, DiseaseRepository $repository): JsonResponse
{ {
$qb = $repository->createFilteredQueryBuilder($request->query->all()); $qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [ return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS, 'groups' => self::READ_GROUPS,
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Controller; namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\MedicalCenter; use App\Entity\MedicalCenter;
use App\Repository\MedicalCenterRepository; use App\Repository\MedicalCenterRepository;
use App\Service\Crud\CrudResponder; use App\Service\Crud\CrudResponder;
@@ -36,7 +37,7 @@ final class MedicalCenterController extends AbstractController
#[Route('/list', name: 'medical_center_list', methods: ['GET'])] #[Route('/list', name: 'medical_center_list', methods: ['GET'])]
public function list(Request $request, MedicalCenterRepository $repository): JsonResponse public function list(Request $request, MedicalCenterRepository $repository): JsonResponse
{ {
$qb = $repository->createFilteredQueryBuilder($request->query->all()); $qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [ return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS, 'groups' => self::READ_GROUPS,
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Controller; namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\News; use App\Entity\News;
use App\Repository\NewsRepository; use App\Repository\NewsRepository;
use App\Service\Crud\CrudResponder; use App\Service\Crud\CrudResponder;
@@ -36,7 +37,7 @@ final class NewsController extends AbstractController
#[Route('/list', name: 'news_list', methods: ['GET'])] #[Route('/list', name: 'news_list', methods: ['GET'])]
public function list(Request $request, NewsRepository $repository): JsonResponse public function list(Request $request, NewsRepository $repository): JsonResponse
{ {
$qb = $repository->createFilteredQueryBuilder($request->query->all()); $qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [ return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS, 'groups' => self::READ_GROUPS,
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Controller; namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Promo; use App\Entity\Promo;
use App\Repository\PromoRepository; use App\Repository\PromoRepository;
use App\Service\Crud\CrudResponder; use App\Service\Crud\CrudResponder;
@@ -36,7 +37,7 @@ final class PromoController extends AbstractController
#[Route('/list', name: 'promo_list', methods: ['GET'])] #[Route('/list', name: 'promo_list', methods: ['GET'])]
public function list(Request $request, PromoRepository $repository): JsonResponse public function list(Request $request, PromoRepository $repository): JsonResponse
{ {
$qb = $repository->createFilteredQueryBuilder($request->query->all()); $qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [ return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS, 'groups' => self::READ_GROUPS,
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Controller; namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\SiteService; use App\Entity\SiteService;
use App\Repository\SiteServiceRepository; use App\Repository\SiteServiceRepository;
use App\Service\Crud\CrudResponder; use App\Service\Crud\CrudResponder;
@@ -36,7 +37,7 @@ final class SiteServiceController extends AbstractController
#[Route('/list', name: 'site_service_list', methods: ['GET'])] #[Route('/list', name: 'site_service_list', methods: ['GET'])]
public function list(Request $request, SiteServiceRepository $repository): JsonResponse public function list(Request $request, SiteServiceRepository $repository): JsonResponse
{ {
$qb = $repository->createFilteredQueryBuilder($request->query->all()); $qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [ return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS, 'groups' => self::READ_GROUPS,
+63
View File
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace App\Dto\Content;
use Symfony\Component\HttpFoundation\Request;
final readonly class ContentFilterDto
{
public function __construct(
public ?int $regionId = null,
public ?bool $active = null,
public ?string $alias = null,
public ?string $search = null,
) {
}
public static function fromRequest(Request $request): self
{
return new self(
regionId: self::positiveInt($request->query->get('regionId', $request->query->get('region_id'))),
active: self::nullableBool($request->query->get('active')),
alias: self::nonEmptyString($request->query->get('alias')),
search: self::nonEmptyString($request->query->get('search', $request->query->get('q'))),
);
}
private static function positiveInt(mixed $value): ?int
{
if ($value === null || $value === '' || !is_numeric($value)) {
return null;
}
$value = (int) $value;
return $value > 0 ? $value : null;
}
private static function nullableBool(mixed $value): ?bool
{
if ($value === null || $value === '') {
return null;
}
if (is_bool($value)) {
return $value;
}
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
private static function nonEmptyString(mixed $value): ?string
{
if (!is_string($value)) {
return null;
}
$value = trim($value);
return $value !== '' ? $value : null;
}
}
+5 -1
View File
@@ -2,6 +2,7 @@
namespace App\Entity; namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\ArticleRepository; use App\Repository\ArticleRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@@ -12,8 +13,11 @@ use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Table(name: 'article')] #[ORM\Table(name: 'article')]
#[ORM\Index(name: 'idx_article_region_id', columns: ['region_id'])] #[ORM\Index(name: 'idx_article_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_article_active', columns: ['active'])] #[ORM\Index(name: 'idx_article_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class Article class Article
{ {
use UpdateTimestampTrait;
#[Groups(['article:read'])] #[Groups(['article:read'])]
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")] #[ORM\GeneratedValue(strategy: "IDENTITY")]
@@ -56,7 +60,7 @@ class Article
#[ORM\Column(type: Types::TEXT, nullable: true)] #[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $content = null; private ?string $content = null;
#[Groups(['article:read', 'article:write'])] #[Groups(['article:read'])]
#[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)] #[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $updateAt = null; private ?\DateTimeInterface $updateAt = null;
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Entity\Behavior;
use Doctrine\ORM\Mapping as ORM;
trait UpdateTimestampTrait
{
#[ORM\PrePersist]
public function setInitialUpdateAt(): void
{
if ($this->updateAt === null) {
$this->updateAt = new \DateTime();
}
}
#[ORM\PreUpdate]
public function refreshUpdateAt(): void
{
$this->updateAt = new \DateTime();
}
}
+5 -1
View File
@@ -2,6 +2,7 @@
namespace App\Entity; namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\DiseaseRepository; use App\Repository\DiseaseRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@@ -11,8 +12,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Table(name: 'disease')] #[ORM\Table(name: 'disease')]
#[ORM\Index(name: 'idx_disease_region_id', columns: ['region_id'])] #[ORM\Index(name: 'idx_disease_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_disease_active', columns: ['active'])] #[ORM\Index(name: 'idx_disease_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class Disease class Disease
{ {
use UpdateTimestampTrait;
#[Groups(['disease:read'])] #[Groups(['disease:read'])]
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")] #[ORM\GeneratedValue(strategy: "IDENTITY")]
@@ -43,7 +47,7 @@ class Disease
#[ORM\Column(type: Types::TEXT, nullable: true)] #[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $anons = null; private ?string $anons = null;
#[Groups(['disease:read', 'disease:write'])] #[Groups(['disease:read'])]
#[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)] #[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $updateAt = null; private ?\DateTimeInterface $updateAt = null;
+5 -1
View File
@@ -2,6 +2,7 @@
namespace App\Entity; namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\MedicalCenterRepository; use App\Repository\MedicalCenterRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@@ -9,8 +10,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: MedicalCenterRepository::class)] #[ORM\Entity(repositoryClass: MedicalCenterRepository::class)]
#[ORM\Table(name: 'medical_center')] #[ORM\Table(name: 'medical_center')]
#[ORM\HasLifecycleCallbacks]
class MedicalCenter class MedicalCenter
{ {
use UpdateTimestampTrait;
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")] #[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)] #[ORM\Column(type: Types::INTEGER)]
@@ -42,7 +46,7 @@ class MedicalCenter
private ?string $content = null; private ?string $content = null;
#[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)] #[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])] #[Groups(['medical_center:read'])]
private ?\DateTimeInterface $updateAt = null; private ?\DateTimeInterface $updateAt = null;
#[ORM\Column(name: 'kod_uslug', type: 'jsonb', nullable: true)] #[ORM\Column(name: 'kod_uslug', type: 'jsonb', nullable: true)]
+5 -1
View File
@@ -2,6 +2,7 @@
namespace App\Entity; namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\NewsRepository; use App\Repository\NewsRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@@ -11,8 +12,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Table(name: 'news')] #[ORM\Table(name: 'news')]
#[ORM\Index(name: 'idx_news_region_id', columns: ['region_id'])] #[ORM\Index(name: 'idx_news_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_news_active', columns: ['active'])] #[ORM\Index(name: 'idx_news_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class News class News
{ {
use UpdateTimestampTrait;
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")] #[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)] #[ORM\Column(type: Types::INTEGER)]
@@ -44,7 +48,7 @@ class News
private ?string $content = null; private ?string $content = null;
#[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)] #[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)]
#[Groups(['news:read', 'news:write'])] #[Groups(['news:read'])]
private ?\DateTimeInterface $updateAt = null; private ?\DateTimeInterface $updateAt = null;
#[ORM\Column(name: 'link_el_price', type: Types::TEXT, nullable: true)] #[ORM\Column(name: 'link_el_price', type: Types::TEXT, nullable: true)]
+5 -1
View File
@@ -2,6 +2,7 @@
namespace App\Entity; namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\PromoRepository; use App\Repository\PromoRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@@ -11,8 +12,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Table(name: 'promo')] #[ORM\Table(name: 'promo')]
#[ORM\Index(name: 'idx_promo_region_id', columns: ['region_id'])] #[ORM\Index(name: 'idx_promo_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_promo_active', columns: ['active'])] #[ORM\Index(name: 'idx_promo_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class Promo class Promo
{ {
use UpdateTimestampTrait;
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")] #[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)] #[ORM\Column(type: Types::INTEGER)]
@@ -44,7 +48,7 @@ class Promo
private ?string $content = null; private ?string $content = null;
#[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)] #[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)]
#[Groups(['promo:read', 'promo:write'])] #[Groups(['promo:read'])]
private ?\DateTimeInterface $updateAt = null; private ?\DateTimeInterface $updateAt = null;
#[ORM\Column(type: 'jsonb', nullable: true)] #[ORM\Column(type: 'jsonb', nullable: true)]
+5 -1
View File
@@ -2,6 +2,7 @@
namespace App\Entity; namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\SiteServiceRepository; use App\Repository\SiteServiceRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@@ -11,8 +12,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Table(name: 'site_services')] #[ORM\Table(name: 'site_services')]
#[ORM\Index(name: 'idx_site_services_region_id', columns: ['region_id'])] #[ORM\Index(name: 'idx_site_services_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_site_services_active', columns: ['active'])] #[ORM\Index(name: 'idx_site_services_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class SiteService class SiteService
{ {
use UpdateTimestampTrait;
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")] #[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)] #[ORM\Column(type: Types::INTEGER)]
@@ -44,7 +48,7 @@ class SiteService
private ?string $content = null; private ?string $content = null;
#[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)] #[ORM\Column(name: 'update_at', type: Types::DATETIME_MUTABLE, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])] #[Groups(['site_service:read'])]
private ?\DateTimeInterface $updateAt = null; private ?\DateTimeInterface $updateAt = null;
#[ORM\Column(name: 'link_videoreviews', type: 'jsonb', nullable: true)] #[ORM\Column(name: 'link_videoreviews', type: 'jsonb', nullable: true)]
+2 -2
View File
@@ -2,6 +2,7 @@
namespace App\Repository; namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Article; use App\Entity\Article;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@@ -20,9 +21,8 @@ class ArticleRepository extends ServiceEntityRepository
} }
/** /**
* @param array<string, mixed> $filters
*/ */
public function createFilteredQueryBuilder(array $filters): QueryBuilder public function createFilteredQueryBuilder(ContentFilterDto $filters): QueryBuilder
{ {
$qb = $this->createQueryBuilder('a')->orderBy('a.id', 'DESC'); $qb = $this->createQueryBuilder('a')->orderBy('a.id', 'DESC');
+10 -89
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Repository; namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
/** /**
@@ -24,107 +25,27 @@ use Doctrine\ORM\QueryBuilder;
trait ContentFilterTrait trait ContentFilterTrait
{ {
/** /**
* @param array<string, mixed> $filters
*/ */
private function applyCommonFilters(QueryBuilder $qb, string $alias, array $filters): void private function applyCommonFilters(QueryBuilder $qb, string $alias, ContentFilterDto $filters): void
{ {
$regionId = $this->extractIntFilter($filters, ['regionId', 'region_id']); if ($filters->regionId !== null) {
if ($regionId !== null && $regionId > 0) {
$qb->andWhere("$alias.regionId = :regionId") $qb->andWhere("$alias.regionId = :regionId")
->setParameter('regionId', $regionId); ->setParameter('regionId', $filters->regionId);
} }
$active = $this->extractBoolFilter($filters, ['active']); if ($filters->active !== null) {
if ($active !== null) {
$qb->andWhere("$alias.active = :active") $qb->andWhere("$alias.active = :active")
->setParameter('active', $active); ->setParameter('active', $filters->active);
} }
$aliasFilter = $this->extractNonEmptyStringFilter($filters, ['alias']); if ($filters->alias !== null) {
if ($aliasFilter !== null) {
$qb->andWhere("$alias.alias = :aliasValue") $qb->andWhere("$alias.alias = :aliasValue")
->setParameter('aliasValue', $aliasFilter); ->setParameter('aliasValue', $filters->alias);
} }
$search = $this->extractNonEmptyStringFilter($filters, ['search', 'q']); if ($filters->search !== null) {
if ($search !== null) {
$qb->andWhere("LOWER($alias.name) LIKE :search") $qb->andWhere("LOWER($alias.name) LIKE :search")
->setParameter('search', '%' . mb_strtolower($search) . '%'); ->setParameter('search', '%' . mb_strtolower($filters->search) . '%');
} }
} }
/**
* @param array<string, mixed> $filters
* @param list<string> $keys
*/
private function extractIntFilter(array $filters, array $keys): ?int
{
foreach ($keys as $key) {
if (!array_key_exists($key, $filters)) {
continue;
}
$value = $filters[$key];
if ($value === null || $value === '') {
continue;
}
if (is_numeric($value)) {
return (int) $value;
}
}
return null;
}
/**
* @param array<string, mixed> $filters
* @param list<string> $keys
*/
private function extractBoolFilter(array $filters, array $keys): ?bool
{
foreach ($keys as $key) {
if (!array_key_exists($key, $filters)) {
continue;
}
$value = $filters[$key];
if ($value === null || $value === '') {
continue;
}
if (is_bool($value)) {
return $value;
}
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
return null;
}
/**
* @param array<string, mixed> $filters
* @param list<string> $keys
*/
private function extractNonEmptyStringFilter(array $filters, array $keys): ?string
{
foreach ($keys as $key) {
if (!array_key_exists($key, $filters)) {
continue;
}
$value = $filters[$key];
if (!is_string($value)) {
continue;
}
$trimmed = trim($value);
if ($trimmed !== '') {
return $trimmed;
}
}
return null;
}
} }
+2 -2
View File
@@ -2,6 +2,7 @@
namespace App\Repository; namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Disease; use App\Entity\Disease;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@@ -23,9 +24,8 @@ class DiseaseRepository extends ServiceEntityRepository
} }
/** /**
* @param array<string, mixed> $filters
*/ */
public function createFilteredQueryBuilder(array $filters): QueryBuilder public function createFilteredQueryBuilder(ContentFilterDto $filters): QueryBuilder
{ {
$qb = $this->createQueryBuilder('d')->orderBy('d.id', 'ASC'); $qb = $this->createQueryBuilder('d')->orderBy('d.id', 'ASC');
+2 -2
View File
@@ -2,6 +2,7 @@
namespace App\Repository; namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use App\Entity\MedicalCenter; use App\Entity\MedicalCenter;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@@ -23,9 +24,8 @@ class MedicalCenterRepository extends ServiceEntityRepository
} }
/** /**
* @param array<string, mixed> $filters
*/ */
public function createFilteredQueryBuilder(array $filters): QueryBuilder public function createFilteredQueryBuilder(ContentFilterDto $filters): QueryBuilder
{ {
$qb = $this->createQueryBuilder('m')->orderBy('m.id', 'DESC'); $qb = $this->createQueryBuilder('m')->orderBy('m.id', 'DESC');
+2 -2
View File
@@ -2,6 +2,7 @@
namespace App\Repository; namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use App\Entity\News; use App\Entity\News;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@@ -27,9 +28,8 @@ class NewsRepository extends ServiceEntityRepository
* *
* Поддерживаемые фильтры: regionId, active (по умолчанию true), alias, search. * Поддерживаемые фильтры: regionId, active (по умолчанию true), alias, search.
* *
* @param array<string, mixed> $filters
*/ */
public function createFilteredQueryBuilder(array $filters): QueryBuilder public function createFilteredQueryBuilder(ContentFilterDto $filters): QueryBuilder
{ {
$qb = $this->createQueryBuilder('n')->orderBy('n.id', 'DESC'); $qb = $this->createQueryBuilder('n')->orderBy('n.id', 'DESC');
+2 -2
View File
@@ -2,6 +2,7 @@
namespace App\Repository; namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Promo; use App\Entity\Promo;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@@ -23,9 +24,8 @@ class PromoRepository extends ServiceEntityRepository
} }
/** /**
* @param array<string, mixed> $filters
*/ */
public function createFilteredQueryBuilder(array $filters): QueryBuilder public function createFilteredQueryBuilder(ContentFilterDto $filters): QueryBuilder
{ {
$qb = $this->createQueryBuilder('p')->orderBy('p.id', 'DESC'); $qb = $this->createQueryBuilder('p')->orderBy('p.id', 'DESC');
+2 -2
View File
@@ -2,6 +2,7 @@
namespace App\Repository; namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use App\Entity\SiteService; use App\Entity\SiteService;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@@ -23,9 +24,8 @@ class SiteServiceRepository extends ServiceEntityRepository
} }
/** /**
* @param array<string, mixed> $filters
*/ */
public function createFilteredQueryBuilder(array $filters): QueryBuilder public function createFilteredQueryBuilder(ContentFilterDto $filters): QueryBuilder
{ {
$qb = $this->createQueryBuilder('s')->orderBy('s.id', 'ASC'); $qb = $this->createQueryBuilder('s')->orderBy('s.id', 'ASC');
+2 -16
View File
@@ -52,22 +52,17 @@ final class CrudResponder
string $entityClass, string $entityClass,
array $writeGroups, array $writeGroups,
array $readGroups, array $readGroups,
bool $allowIdFromPayload = false,
): JsonResponse { ): JsonResponse {
$payload = $this->decodePayload($request); $payload = $this->decodePayload($request);
if ($payload === null) { if ($payload === null) {
return $this->jsonError('Ожидается JSON-объект в теле запроса', Response::HTTP_BAD_REQUEST); return $this->jsonError('Ожидается JSON-объект в теле запроса', Response::HTTP_BAD_REQUEST);
} }
unset($payload['id']);
$deserializationPayload = $payload;
if (!$allowIdFromPayload) {
unset($deserializationPayload['id']);
}
try { try {
/** @var T $entity */ /** @var T $entity */
$entity = $this->serializer->deserialize( $entity = $this->serializer->deserialize(
$this->encodePayload($deserializationPayload), $this->encodePayload($payload),
$entityClass, $entityClass,
'json', 'json',
[ [
@@ -78,15 +73,6 @@ final class CrudResponder
return $this->jsonError('Ошибка десериализации: ' . $e->getMessage(), Response::HTTP_BAD_REQUEST); return $this->jsonError('Ошибка десериализации: ' . $e->getMessage(), Response::HTTP_BAD_REQUEST);
} }
// По умолчанию публичный CRUD не принимает id от клиента. Если системной
// интеграции понадобится внешний id, конкретный вызов должен явно передать true.
if ($allowIdFromPayload && isset($payload['id']) && method_exists($entity, 'setId')) {
$id = (int) $payload['id'];
if ($id > 0) {
$entity->setId($id);
}
}
if (($validationResponse = $this->validate($entity)) !== null) { if (($validationResponse = $this->validate($entity)) !== null) {
return $validationResponse; return $validationResponse;
} }