Merge branch 'main' into feat/project-budget
This commit is contained in:
@@ -38,7 +38,7 @@ final class LocalizedCollectionExtension implements QueryResultCollectionExtensi
|
||||
?Operation $operation = null,
|
||||
array $context = [],
|
||||
): iterable {
|
||||
$query = $this->addLocalizationHints($queryBuilder, $this->getContextLanguages($context));
|
||||
$query = $this->addLocalizationHints($queryBuilder, $this->getAcceptedLanguages($context));
|
||||
|
||||
if (\count($queryBuilder->getAllAliases()) === 1) {
|
||||
$query->setHint(CountWalker::HINT_DISTINCT, false);
|
||||
|
||||
@@ -3,17 +3,26 @@
|
||||
namespace App\Doctrine;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use App\Entity\Interface\LocalizedContentInterface;
|
||||
use App\Entity\Interface\LocalizedEntityInterface;
|
||||
use App\Service\LocalizationService;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Gedmo\Translatable\TranslatableListener;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
trait LocalizedExtensionTrait
|
||||
{
|
||||
protected RequestStack $requestStack;
|
||||
|
||||
protected LocalizationService $localizationService;
|
||||
|
||||
#[Required]
|
||||
public function setRequestStack(RequestStack $requestStack)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
#[Required]
|
||||
public function setLocalizationService(LocalizationService $localizationService)
|
||||
{
|
||||
@@ -24,7 +33,7 @@ trait LocalizedExtensionTrait
|
||||
{
|
||||
$reflectionClass = new \ReflectionClass($resourceClass);
|
||||
|
||||
return $reflectionClass->implementsInterface(LocalizedContentInterface::class);
|
||||
return $reflectionClass->implementsInterface(LocalizedEntityInterface::class);
|
||||
}
|
||||
|
||||
private function addLocalizationHints(QueryBuilder $queryBuilder, array $locales): Query
|
||||
@@ -46,9 +55,15 @@ trait LocalizedExtensionTrait
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function getContextLanguages(array $context): array
|
||||
private function getAcceptedLanguages(array $context): array
|
||||
{
|
||||
$tags = $context['request']->headers->get('Accept-Language', '');
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
if (\array_key_exists('request', $context)) {
|
||||
$request = $context['request'];
|
||||
}
|
||||
|
||||
$tags = $request->headers->get('Accept-Language', '');
|
||||
|
||||
return $this->localizationService->getLanguages($tags);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ final class LocalizedItemExtension implements QueryResultItemExtensionInterface
|
||||
?Operation $operation = null,
|
||||
array $context = [],
|
||||
): ?object {
|
||||
$query = $this->addLocalizationHints($queryBuilder, $this->getContextLanguages($context));
|
||||
$query = $this->addLocalizationHints($queryBuilder, $this->getAcceptedLanguages($context));
|
||||
|
||||
return $query->getOneOrNullResult();
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ use Gedmo\Translatable\Translatable;
|
||||
*
|
||||
* @see https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/translatable.md#translatable-behavior-extension-for-doctrine
|
||||
*/
|
||||
interface LocalizedContentInterface extends Translatable
|
||||
interface LocalizedEntityInterface extends Translatable
|
||||
{
|
||||
public function getId(): ?int;
|
||||
|
||||
@@ -4,9 +4,9 @@ namespace App\Entity\Project;
|
||||
|
||||
use App\Entity\Accounting\Accounting;
|
||||
use App\Entity\Interface\AccountingOwnerInterface;
|
||||
use App\Entity\Interface\LocalizedContentInterface;
|
||||
use App\Entity\Interface\LocalizedEntityInterface;
|
||||
use App\Entity\Interface\UserOwnedInterface;
|
||||
use App\Entity\Trait\LocalizedContent;
|
||||
use App\Entity\Trait\LocalizedEntityTrait;
|
||||
use App\Entity\Trait\MigratedEntity;
|
||||
use App\Entity\Trait\TimestampedCreationEntity;
|
||||
use App\Entity\Trait\TimestampedUpdationEntity;
|
||||
@@ -23,9 +23,9 @@ use Gedmo\Mapping\Annotation as Gedmo;
|
||||
|
||||
#[MapProvider(EntityMapProvider::class)]
|
||||
#[ORM\Entity(repositoryClass: ProjectRepository::class)]
|
||||
class Project implements UserOwnedInterface, AccountingOwnerInterface, LocalizedContentInterface
|
||||
class Project implements UserOwnedInterface, AccountingOwnerInterface, LocalizedEntityInterface
|
||||
{
|
||||
use LocalizedContent;
|
||||
use LocalizedEntityTrait;
|
||||
use MigratedEntity;
|
||||
use TimestampedCreationEntity;
|
||||
use TimestampedUpdationEntity;
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Entity\Trait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
|
||||
trait LocalizedContent
|
||||
trait LocalizedEntityTrait
|
||||
{
|
||||
#[Gedmo\Locale]
|
||||
private ?string $locale = null;
|
||||
@@ -33,8 +33,8 @@ The v4 API accepts localization of content. Resources such as Projects can have
|
||||
|
||||
Retrieval of content in different locales is performed via standard HTTP [content negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation). When the request supplies an `Accept-Language` header, the API will retrieve localized versions of the content where available, falling back to the default locale of the API instance, and finally to the first available localization of the content regardless of the request preferences or the API defaults if it cannot find suitable localizations.
|
||||
|
||||
Submission of localized content is performed over standard REST content submission with the addition of a `Content-Language` header to a POST, PUT or PATCH request. Only the first tag will be used to determine the submitted localization.
|
||||
Submission of localized content is performed over standard REST content submission with the addition of a `Content-Language` header to a POST, PUT or PATCH request. Only the first tag will be used to determine the submitted localization. When a localizable resource is created with a non-localized request (a request without a `Content-Language` header), the default locale of the API instance is used.
|
||||
|
||||
Removal of localized content is performed over standard REST content deletion with the addition of a `Content-Language` header to a DELETE request. A delete request for localized content will act partially and remove only the requested localizations, not the entire resource. To remove the resource you can send a non-localized request omitting the `Content-Language` header. Unlike on submission v4 will process all language tags included in the header and remove every matching localization.
|
||||
Removal of localized content is performed over standard REST content deletion with the addition of a `Content-Language` header to a DELETE request. A delete request for localized content will act partially and remove only the requested localizations, not the entire resource. To remove the resource you can send a non-localized request. Unlike on submission v4 will process all language tags included in the header and remove every matching localization.
|
||||
|
||||
Localizations are assumed to be one per language globally. Language tags will be parsed to match to an [ISO 639](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) two-letter languade code, e.g: a value of "en_US" will be converted to "en", ignoring the "US" subtag.
|
||||
Localizations are assumed to be one per language globally. Language tags will be parsed to match to an [ISO 639](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) two-letter languade code, e.g: a value of "en_US, en_GB" will be converted to "en", ignoring the "US" and "GB" subtags.
|
||||
|
||||
@@ -2,22 +2,15 @@
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Doctrine\Common\State\PersistProcessor;
|
||||
use ApiPlatform\Doctrine\Common\State\RemoveProcessor;
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata\DeleteOperationInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Mapping\AutoMapper;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
class ApiResourceStateProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire(service: RemoveProcessor::class)]
|
||||
private ProcessorInterface $deleteProcessor,
|
||||
#[Autowire(service: PersistProcessor::class)]
|
||||
private ProcessorInterface $persistProcessor,
|
||||
private EntityStateProcessor $entityStateProcessor,
|
||||
private AutoMapper $autoMapper,
|
||||
) {}
|
||||
|
||||
@@ -28,14 +21,12 @@ class ApiResourceStateProcessor implements ProcessorInterface
|
||||
{
|
||||
$entity = $this->getEntity($data, $operation->getStateOptions());
|
||||
|
||||
if ($operation instanceof DeleteOperationInterface) {
|
||||
$this->deleteProcessor->process($entity, $operation, $uriVariables, $context);
|
||||
$entity = $this->entityStateProcessor->process($entity, $operation, $uriVariables, $context);
|
||||
|
||||
if ($entity === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->persistProcessor->process($entity, $operation, $uriVariables, $context);
|
||||
|
||||
return $this->autoMapper->map($entity, $data);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,20 +7,20 @@ use ApiPlatform\Doctrine\Common\State\RemoveProcessor;
|
||||
use ApiPlatform\Metadata\DeleteOperationInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\Interface\LocalizedContentInterface;
|
||||
use App\Service\LocalizationService;
|
||||
use App\Entity\Interface\LocalizedEntityInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Gedmo\Translatable\Entity\Translation;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
class EntityStateProcessor implements ProcessorInterface
|
||||
{
|
||||
use LocalizedStateProcessorTrait;
|
||||
|
||||
public function __construct(
|
||||
#[Autowire(service: RemoveProcessor::class)]
|
||||
private ProcessorInterface $deleteProcessor,
|
||||
#[Autowire(service: PersistProcessor::class)]
|
||||
private ProcessorInterface $persistProcessor,
|
||||
private LocalizationService $localizationService,
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {}
|
||||
|
||||
@@ -29,8 +29,8 @@ class EntityStateProcessor implements ProcessorInterface
|
||||
*/
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
|
||||
{
|
||||
if ($data instanceof LocalizedContentInterface) {
|
||||
return $this->processLocalizedContent($data, $operation, $uriVariables, $context);
|
||||
if ($this->isLocalizedData($data)) {
|
||||
return $this->processLocalizedData($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
|
||||
if ($operation instanceof DeleteOperationInterface) {
|
||||
@@ -40,42 +40,31 @@ class EntityStateProcessor implements ProcessorInterface
|
||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
|
||||
private function processLocalizedContent(
|
||||
LocalizedContentInterface $data,
|
||||
private function processLocalizedData(
|
||||
LocalizedEntityInterface $data,
|
||||
Operation $operation,
|
||||
array $uriVariables = [],
|
||||
array $context = [],
|
||||
): mixed {
|
||||
$languages = $this->getContextLanguages($context);
|
||||
$languages = $this->getContentLanguages($context);
|
||||
|
||||
if ($operation instanceof DeleteOperationInterface) {
|
||||
if ($languages === null) {
|
||||
return $this->deleteProcessor->process($data, $operation, $uriVariables, $context);
|
||||
if ($this->isLocalizedRequest($context)) {
|
||||
return $this->deleteLocalizedContent($data, $operation, $uriVariables, $context, $languages);
|
||||
}
|
||||
|
||||
return $this->deleteLocalizedContent($data, $operation, $uriVariables, $context, $languages);
|
||||
return $this->deleteProcessor->process($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
|
||||
$language = $languages ? $languages[0] : $this->localizationService->getDefaultLanguage();
|
||||
$language = $languages[0] ?? $this->localizationService->getDefaultLanguage();
|
||||
|
||||
$data->setTranslatableLocale($language);
|
||||
|
||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||
}
|
||||
|
||||
private function getContextLanguages(array $context): ?array
|
||||
{
|
||||
$tags = $context['request']->headers->get('Content-Language', '');
|
||||
|
||||
if (empty($tags)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->localizationService->getLanguages($tags);
|
||||
}
|
||||
|
||||
private function deleteLocalizedContent(
|
||||
LocalizedContentInterface $data,
|
||||
LocalizedEntityInterface $data,
|
||||
Operation $operation,
|
||||
array $uriVariables = [],
|
||||
array $context = [],
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use App\Entity\Interface\LocalizedEntityInterface;
|
||||
use App\Service\LocalizationService;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
trait LocalizedStateProcessorTrait
|
||||
{
|
||||
protected RequestStack $requestStack;
|
||||
|
||||
protected LocalizationService $localizationService;
|
||||
|
||||
#[Required]
|
||||
public function setRequestStack(RequestStack $requestStack)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
#[Required]
|
||||
public function setLocalizationService(LocalizationService $localizationService)
|
||||
{
|
||||
$this->localizationService = $localizationService;
|
||||
}
|
||||
|
||||
private function isLocalizedData(mixed $data): bool
|
||||
{
|
||||
if ($data instanceof LocalizedEntityInterface) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isLocalizedRequest(array $context): bool
|
||||
{
|
||||
if (\array_key_exists('request', $context)) {
|
||||
return $context['request']->headers->has('Content-Language');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getContentLanguages(array $context): ?array
|
||||
{
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
if (\array_key_exists('request', $context)) {
|
||||
$request = $context['request'];
|
||||
}
|
||||
|
||||
$tags = $request->headers->get('Content-Language');
|
||||
|
||||
if ($tags === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->localizationService->getLanguages($tags);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user