<?php
namespace App\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\Form\FormUtils;
use App\Service\PreSendSerializer;
use App\Service\Provider\ApiProviderInterface;
use App\Service\TokenDataWrapper\TokenDataWrapper;
use App\V4\DataPersister\AbstractWithoutRequestDataPersister;
use App\V4\Event\PostPersistEvent;
use App\V4\Event\PostRemoveEvent;
use App\V4\Form\Type\Prospect\ProspectBusinessType;
use App\V4\Form\Type\Prospect\ProspectIndividualType;
use App\V4\Logger\SentryLogger;
use App\V4\Messenger\Message\ResynchronizeBadgesRequest;
use Doctrine\Common\Annotations\AnnotationException;
use LogicException;
use Psr\Cache\InvalidArgumentException;
use Psr\Log\LogLevel;
use RuntimeException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
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 AbstractDataPersister implements ContextAwareDataPersisterInterface
{
use FormUtils;
protected const ENTITY = null;
protected const FORM_TYPE = null;
protected const ENDPOINT = '/';
protected const POST_SERIALIZATION_GROUPS = [];
protected const PUT_SERIALIZATION_GROUPS = [];
public const CONTEXT_ORIGIN = 'origin';
/**
* @var ApiWebService
*/
protected $apiWebService;
/**
* @var ApiProviderInterface
*/
protected $apiProvider;
/**
* @var TokenDataWrapper
*/
protected $tokenDataWrapper;
/**
* @var BasicDataWrapper
*/
protected $basicDataWrapper;
/**
* @var RequestStack
*/
protected $request;
/**
* @var FormFactoryInterface
*/
protected $formFactory;
/**
* @var PreSendSerializer
*/
protected $serializer;
/**
* @var CacheManager
*/
protected $cacheManager;
/**
* @var SentryLogger
*/
protected $sentryLogger;
/**
* @var MessageBusInterface
*/
private $bus;
/**
* @var Security
*/
private $security;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @param RequestStack $request
* @param ApiWebService $apiWebService
* @param ApiProviderInterface $apiProvider
* @param TokenDataWrapper $tokenDataWrapper
* @param BasicDataWrapper $basicDataWrapper
* @param FormFactoryInterface $formFactory
* @param PreSendSerializer $serializer
* @param CacheManager $cacheManager
* @param MessageBusInterface $bus
* @param Security $security
* @param EventDispatcherInterface $eventDispatcher
* @param SentryLogger $sentryLogger
*/
public function __construct(
RequestStack $request,
ApiWebService $apiWebService,
ApiProviderInterface $apiProvider,
TokenDataWrapper $tokenDataWrapper,
BasicDataWrapper $basicDataWrapper,
FormFactoryInterface $formFactory,
PreSendSerializer $serializer,
CacheManager $cacheManager,
MessageBusInterface $bus,
Security $security,
EventDispatcherInterface $eventDispatcher,
SentryLogger $sentryLogger
) {
$this->request = $request;
$this->apiWebService = $apiWebService;
$this->apiProvider = $apiProvider;
$this->tokenDataWrapper = $tokenDataWrapper;
$this->basicDataWrapper = $basicDataWrapper;
$this->formFactory = $formFactory;
$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[AbstractWithoutRequestDataPersister::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
* @throws \Exception
*/
public function persist($data, array $context = [])
{
// using serialize method to deep clone, as simple clone did not work ¯\_(ツ)_/¯
$dataBefore = is_object($data) ? unserialize(serialize($data)) : null;
$isEdit = ($data && $data->getId()) || isset($context['id']);
//try {
$request = $this->request->getCurrentRequest();
if (!$request) {
throw new LogicException('There was not request, which is unexpected');
}
$url = $this::ENDPOINT;
$serializationGroups = $this::POST_SERIALIZATION_GROUPS;
$method = Request::METHOD_POST;
if ($isEdit) {
$method = Request::METHOD_PUT;
$url = sprintf('%s/%s', $this::ENDPOINT, $context['id'] ?? $data->getId());
$serializationGroups = $this::PUT_SERIALIZATION_GROUPS;
}
$options = $request->get('prospectId') ? ['prospectId' => $request->get('prospectId')] : [];
if ($request->get('quoteId')) {
$options['quoteId'] = $request->get('quoteId');
}
$form = $this->formFactory->create($this::FORM_TYPE, $data, $options);
$submittedData = $request->getContent()
? json_decode($request->getContent(), true)
: array_merge_recursive($request->files->all(), $request->request->all())
;
if (!empty($request->get('prospectId'))) {
if ($form->has('prospect') && !isset($submittedData['prospect'])) {
$submittedData['prospect'] = $request->get('prospectId');
} elseif ($form->has('prospectId') && !isset($submittedData['prospectId'])) {
$submittedData['prospectId'] = $request->get('prospectId');
}
}
if (!empty($request->get('quoteId'))) {
if ($form->has('quotes') && !isset($submittedData['quotes'])) {
$submittedData['quotes'] = [$request->get('quoteId')];
}
}
$submittedData = $this->reformatSubmittedData($submittedData, $request);
$form->submit($context['dataAlreadySubmitted'] ?? $submittedData, false);
if ($form->isSubmitted() && !$form->isValid()) {
$this->sentryLogger->captureMessage(
SentryLogger::CHANNEL_DATA_PERSISTER,
sprintf('Cannot persist entity of model %s', $this::ENTITY),
[
'catchOnClass' => get_class($this),
'objectId' => $isEdit ? $data->getId() : null,
'entity' => $this::ENTITY,
'requestContent' => $request->getContent()
? json_decode($request->getContent(), true)
: $request->request->all(),
'context' => $context,
'form' => $this->getFormErrorsForLog($form, $submittedData),
],
LogLevel::ERROR
);
return new JsonResponse($this->convertFormErrorsToArrayV4($form), Response::HTTP_UNPROCESSABLE_ENTITY);
}
// Add customerId, userId, createdAt, createdBy, updatedAt, updatedBy information
$requestContent = $this->serializer->serialize($form->getData(), $serializationGroups);
$serializedData = $this->basicDataWrapper->wrapData($requestContent);
$serializedData = $this->tokenDataWrapper->wrapData($serializedData);
$response = $this
->apiWebService
->send($this->apiProvider, $method, $url, $serializedData)
;
$this->cacheManager->invalidateTag([$this->cacheManager->getCustomerPrefix().'_'.$this::ENTITY]);
$entity = $form->getData();
$entity->setId($response->toArray()['id']);
$this->eventDispatcher->dispatch(
(new PostPersistEvent())->setBefore($dataBefore)->setAfter($entity)->setContext($context),
PostPersistEvent::NAME
);
$this->resynchronizeBadges();
return $entity;
/*} catch (Throwable | InvalidArgumentException $exception) {
captureException($exception);
throw new RuntimeException('Unable to persist '.$this::ENTITY, 0, $exception);
}*/
}
/**
* @param $data
* @param array $context
*
* @return void
*
* @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()), [])
;
if (Response::HTTP_OK === $response->getStatusCode() || Response::HTTP_NO_CONTENT === $response->getStatusCode()) {
$this->eventDispatcher->dispatch((new PostRemoveEvent())->setAfter($data), PostRemoveEvent::NAME);
}
$this->resynchronizeBadges();
$this->cacheManager->invalidateTag([$this::ENTITY]);
} catch (Throwable | InvalidArgumentException $exception) {
$this->sentryLogger->captureException(
SentryLogger::CHANNEL_DATA_PERSISTER,
$exception,
[
'catchOnClass' => self::class,
'apiCalled' => $this->apiProvider->getHost(),
'urlCalled' => sprintf('%s/%s', $this::ENDPOINT, $data->getId()),
'method' => Request::METHOD_DELETE,
'message' => 'Unable to delete '.$this::ENTITY.' with id '.$data->getId(),
]
);
throw new RuntimeException('Unable to delete '.$this::ENTITY.' with id '.$data->getId(), 0, $exception);
}
}
/**
* @param $submittedData
* @param Request|null $request
*
* @return array
*/
protected function reformatSubmittedData($submittedData, ?Request $request = null): array
{
$submittedDataFormatted = [];
foreach ($submittedData as $fieldName => $value) {
//@todo refacto V4 Admin : cas à supprimer lorsque les formulaires admin (ViewOrder) seront en v4
if (ProspectBusinessType::class === $this::FORM_TYPE || ProspectIndividualType::class === $this::FORM_TYPE) {
$fieldName = str_replace('unmappedAddress_', 'mainAddress_', $fieldName);
}
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;
}
/**
* @return void
*/
public function resynchronizeBadges()
{
$user = $this->security->getUser();
if ($user instanceof User) {
$this->bus->dispatch((new ResynchronizeBadgesRequest())->setCustomerId($user->getCustomerId()));
}
}
}