<?php
namespace App\V4\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\Security\User;
use App\Service\ApiWebService;
use App\Service\Cache\CacheManager;
use App\Service\DataWrapper\BasicDataWrapper;
use App\Service\PreSendSerializer;
use App\Service\Provider\ApiProviderInterface;
use App\Service\TokenDataWrapper\TokenDataWrapper;
use App\V4\Event\PostPersistEvent;
use App\V4\Event\PostRemoveEvent;
use App\V4\Logger\SentryLogger;
use App\V4\Messenger\Message\ResynchronizeBadgesRequest;
use Doctrine\Common\Annotations\AnnotationException;
use Psr\Cache\InvalidArgumentException;
use RuntimeException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Throwable;
abstract class AbstractWithoutRequestDataPersister implements ContextAwareDataPersisterInterface
{
protected const ENTITY = null;
protected const ENDPOINT = '/';
protected const POST_SERIALIZATION_GROUPS = [];
protected const PUT_SERIALIZATION_GROUPS = [];
public const CONTEXT_IS_V4 = 'is_v4';
public const CONTEXT_NOT_POST_PERSIST_EVENT = 'not_post_persist_event';
/**
* @var ApiWebService
*/
private $apiWebService;
/**
* @var ApiProviderInterface
*/
private $apiProvider;
/**
* @var TokenDataWrapper
*/
private $tokenDataWrapper;
/**
* @var BasicDataWrapper
*/
private $basicDataWrapper;
/**
* @var PreSendSerializer
*/
private $serializer;
/**
* @var CacheManager
*/
private $cacheManager;
/**
* @var MessageBusInterface
*/
private $bus;
/**
* @var Security
*/
private $security;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @var SentryLogger
*/
private $sentryLogger;
/**
* @param ApiWebService $apiWebService
* @param ApiProviderInterface $apiProvider
* @param TokenDataWrapper $tokenDataWrapper
* @param BasicDataWrapper $basicDataWrapper
* @param PreSendSerializer $serializer
* @param CacheManager $cacheManager
* @param MessageBusInterface $bus
* @param Security $security
* @param EventDispatcherInterface $eventDispatcher
* @param SentryLogger $sentryLogger
*/
public function __construct(
ApiWebService $apiWebService,
ApiProviderInterface $apiProvider,
TokenDataWrapper $tokenDataWrapper,
BasicDataWrapper $basicDataWrapper,
PreSendSerializer $serializer,
CacheManager $cacheManager,
MessageBusInterface $bus,
Security $security,
EventDispatcherInterface $eventDispatcher,
SentryLogger $sentryLogger
) {
$this->apiWebService = $apiWebService;
$this->apiProvider = $apiProvider;
$this->tokenDataWrapper = $tokenDataWrapper;
$this->basicDataWrapper = $basicDataWrapper;
$this->serializer = $serializer;
$this->cacheManager = $cacheManager;
$this->bus = $bus;
$this->security = $security;
$this->eventDispatcher = $eventDispatcher;
$this->sentryLogger = $sentryLogger;
}
/**
* @param $data
* @param array $context
*
* @return bool
*/
public function supports($data, array $context = []): bool
{
$entity = $this::ENTITY;
return $data instanceof $entity && isset($context[self::CONTEXT_IS_V4]);
}
/**
* @param object $data
* @param array $context
*
* @return object
*
* @throws InvalidArgumentException
* @throws AnnotationException
* @throws ExceptionInterface
* @throws ClientExceptionInterface
* @throws DecodingExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ServerExceptionInterface
* @throws TransportExceptionInterface
*/
public function persist($data, array $context = [])
{
$url = $this::ENDPOINT;
$serializationGroups = $this::POST_SERIALIZATION_GROUPS;
$method = Request::METHOD_POST;
if (null !== $data->getId()) {
$method = Request::METHOD_PUT;
$url = sprintf('%s/%s', $this::ENDPOINT, $data->getId());
$serializationGroups = $this::PUT_SERIALIZATION_GROUPS;
}
$requestContent = $this->serializer->serialize($data, $serializationGroups);
$requestContent = $this->reformatSubmittedData($requestContent);
$serializedData = $this->basicDataWrapper->wrapData($requestContent);
$serializedData = $this->tokenDataWrapper->wrapData($serializedData);
$response = $this
->apiWebService
->send($this->apiProvider, $method, $url, $serializedData)
;
$data->setId($response->toArray()['id']);
$this->cacheManager->invalidateTag([$this->cacheManager->getCustomerPrefix().'_'.$this::ENTITY]);
if (!isset($context[self::CONTEXT_NOT_POST_PERSIST_EVENT])) {
$this->eventDispatcher->dispatch(
(new PostPersistEvent())->setAfter($data)->setContext($context),
PostPersistEvent::NAME
);
}
$user = $this->security->getUser();
if ($user instanceof User) {
$this->bus->dispatch((new ResynchronizeBadgesRequest())
->setCustomerId($user->getCustomerId()));
}
return $data;
}
/**
* @param $data
* @param array $context
*
* @return void
*
* @throws TransportExceptionInterface
* @throws \Exception
*/
public function remove($data, array $context = []): void
{
try {
$response = $this
->apiWebService
->send($this->apiProvider, Request::METHOD_DELETE, sprintf('%s/%s', $this::ENDPOINT, $data->getId()))
;
$this->cacheManager->invalidateTag([$this::ENTITY]);
if (Response::HTTP_OK === $response->getStatusCode() || Response::HTTP_NO_CONTENT === $response->getStatusCode()) {
$this->eventDispatcher->dispatch((new PostRemoveEvent())->setAfter($data), PostRemoveEvent::NAME);
}
} catch (Throwable | InvalidArgumentException $exception) {
$this->sentryLogger->captureException(
SentryLogger::CHANNEL_DATA_PERSISTER,
$exception,
[
'catchOnClass' => self::class,
'apiCalled' => $this->apiProvider->getHost(),
'urlCalled' => sprintf('%s/%s', self::ENDPOINT, $data->getId()),
'method' => Request::METHOD_DELETE,
'message' => 'Unable to delete '.self::ENTITY.' with id '.$data->getId(),
]
);
throw new RuntimeException('Unable to delete '.$this::ENTITY.' with id '.$data->getId(), 0, $exception);
}
}
/**
* @param $submittedData
*
* @return array
*/
protected function reformatSubmittedData($submittedData): array
{
$submittedDataFormatted = [];
foreach ($submittedData as $fieldName => $value) {
if (str_starts_with($fieldName, 'sf_')) {
$submittedDataFormatted = array_merge($submittedDataFormatted, [$fieldName => $value]);
continue;
}
$temp = &$submittedDataFormatted;
foreach (explode('_', $fieldName) as $key) {
$temp = &$temp[$key];
}
$temp = $value;
}
return $submittedDataFormatted;
}
}