src/Service/Cache/CacheManager.php line 124

Open in your IDE?
  1. <?php
  2. namespace App\Service\Cache;
  3. use App\Form\Type\Task\TaskType;
  4. use App\Model\Civility\Civility;
  5. use App\Model\Field\Field;
  6. use App\Model\GetParamsRequest\GetParamsRequest;
  7. use App\Model\LegalForm\LegalForm;
  8. use App\Model\Listing\Listing;
  9. use App\Model\Origin\Origin;
  10. use App\Model\Potential\Potential;
  11. use App\Model\ProspectType\ProspectType;
  12. use App\Model\Quote\Quote;
  13. use App\Model\QuoteState\QuoteState;
  14. use App\Model\Service\Service;
  15. use App\Model\StructureType\StructureType;
  16. use App\Model\TaskName\TaskName;
  17. use App\Model\TaskType\TaskType as TaskTypeModel;
  18. use App\Model\TVA\TVA;
  19. use App\Model\Unit\Unit;
  20. use App\Model\Vat\Vat;
  21. use App\Model\ViewOrder\ViewOrder;
  22. use App\Model\Warranty\Warranty;
  23. use App\Model\Workforce\Workforce;
  24. use App\Security\User;
  25. use App\Service\Cache\Exception\UnableToSaveKeyException;
  26. use App\V4\Model\Civility\Civility as CivilityV4;
  27. use App\V4\Model\LegalForm\LegalForm as LegalFormV4;
  28. use App\V4\Model\Listing\Listing as ListingV4;
  29. use App\V4\Model\Origin\Origin as OriginV4;
  30. use App\V4\Model\Potential\Potential as PotentialV4;
  31. use App\V4\Model\ProspectType\ProspectType as ProspectTypeV4;
  32. use App\V4\Model\QuoteReason\QuoteReason as QuoteReasonV4;
  33. use App\V4\Model\QuoteState\QuoteState as QuoteStateV4;
  34. use App\V4\Model\Service\Service as ServiceV4;
  35. use App\V4\Model\StructureType\StructureType as StructureTypeV4;
  36. use App\V4\Model\TVA\TVA as TVAV4;
  37. use App\V4\Model\Workforce\Workforce as WorkforceV4;
  38. use DateTime;
  39. use Psr\Cache\CacheException;
  40. use Psr\Cache\InvalidArgumentException;
  41. use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
  42. use Symfony\Component\HttpFoundation\Request;
  43. use Symfony\Component\HttpFoundation\RequestStack;
  44. use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
  45. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
  46. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  47. use Symfony\Contracts\Cache\ItemInterface;
  48. class CacheManager
  49. {
  50.     public const CACHE_ACTION_KEY_LIST 'list';
  51.     public const CACHE_ACTION_KEY_LIST_ACTIVE 'list_active';
  52.     public const CACHE_ACTION_KEY_ITEM 'item';
  53.     protected const REDIS_KEY_PATTERN '%s__%s__%s_%s'// {env}__customer_{id_customer}__{entity}_{action}
  54.     protected const DEFAULT_TTL 3600 10;
  55.     protected const DEFAULT_TTL_GRACE 900;
  56.     private const MEMORY_CACHE_LIMIT 25;
  57.     /** @var TokenStorage */
  58.     protected $tokenStorage;
  59.     /** @var string */
  60.     protected $environment;
  61.     /** @var TagAwareAdapterInterface */
  62.     protected $adapter;
  63.     /**
  64.      * @var Request
  65.      */
  66.     private $requestStack;
  67.     /**
  68.      * Used to prevent multiple calls to Redis for the same key.
  69.      */
  70.     private $memoryCache = [];
  71.     public function __construct(
  72.         TokenStorage $tokenStorage,
  73.         TagAwareAdapterInterface $adapter,
  74.         RequestStack $request,
  75.         string $environment
  76.     ) {
  77.         $this->tokenStorage $tokenStorage;
  78.         $this->adapter $adapter;
  79.         $this->environment $environment;
  80.         $this->requestStack $request;
  81.     }
  82.     /**
  83.      * Generates the cache key name from the entity and action.
  84.      */
  85.     public function getKeyName(string $entityNamestring $action '', ?string $customerId null, ?string $id null): string
  86.     {
  87.         $cacheKey sprintf(
  88.             self::REDIS_KEY_PATTERN,
  89.             $this->environment,
  90.             $this->getCustomerPrefix($customerId),
  91.             $entityName,
  92.             $action
  93.         ).($id '_'.$id '');
  94.         return $this->replaceReservedCharacters($cacheKey);
  95.     }
  96.     /**
  97.      * Fetches a key from the cache.
  98.      *
  99.      * @throws InvalidArgumentException
  100.      */
  101.     public function get(string $entityNamestring $action, ?string $id null)
  102.     {
  103.         if ($this->requestStack->getCurrentRequest() instanceof Request && $this->requestStack->getCurrentRequest()->query->has('delcache')) {
  104.             return null;
  105.         }
  106.         $key $this->getKeyName($entityName$actionnull$id);
  107.         if (isset($this->memoryCache[$key])) {
  108.             return $this->memoryCache[$key];
  109.         }
  110.         $cache $this->adapter->getItem($key);
  111.         if ($cache->isHit()) {
  112.             $value $cache->get();
  113.             $this->memoryCache[$key] = is_array($value) ? $this->wrapResponse($value$cache->getMetadata()) : $value;
  114.             if (count($this->memoryCache) >= self::MEMORY_CACHE_LIMIT) {
  115.                 array_shift($this->memoryCache);
  116.             }
  117.             return $this->memoryCache[$key];
  118.         }
  119.         return null;
  120.     }
  121.     /**
  122.      * Add isCache and cacheDate fields to the response.
  123.      */
  124.     public function wrapResponse(array $data, array $metaData): array
  125.     {
  126.         $date $this->getCacheDate($metaData);
  127.         return array_merge($data, ['isCache' => true'cacheDate' => $date]);
  128.     }
  129.     public function getCacheDate(array $metaData)
  130.     {
  131.         $date null;
  132.         if (isset($metaData['tags'])) {
  133.             // Search date key
  134.             $result array_filter($metaData['tags'], function ($item) {
  135.                 if (false !== stripos($item'utcdatetime_')) {
  136.                     return true;
  137.                 }
  138.                 return false;
  139.             });
  140.             if (is_array($result) && count($result) > 0) {
  141.                 $date explode('_'array_pop($result));
  142.                 $date end($date);
  143.             }
  144.         }
  145.         return $date;
  146.     }
  147.     /**
  148.      * Adds / Overrides a key in the cache.
  149.      *
  150.      * @throws CacheException
  151.      * @throws InvalidArgumentException
  152.      * @throws UnableToSaveKeyException
  153.      */
  154.     public function set(string $entityNamestring $action$value, ?string $id null, ?int $expiresInSec self::DEFAULT_TTL): void
  155.     {
  156.         $item $this->adapter->getItem($this->getKeyName($entityName$actionnull$id));
  157.         $tags = [
  158.             $this->environment,
  159.             $entityName,
  160.             $action,
  161.             sprintf('%s_%s'$entityName$action),
  162.         ];
  163.         $item->tag($this->getCustomerPrefix());
  164.         $item->tag($this->replaceReservedCharacters($entityName));
  165.         foreach ($tags as $tag) {
  166.             $item->tag($this->replaceReservedCharacters($tag));
  167.             $item->tag($this->replaceReservedCharacters(sprintf('%s_%s'$this->getCustomerPrefix(), $tag)));
  168.             // Tag the current date
  169.             $item->tag('utcdatetime_'.(new DateTime())->format('Y-m-d-H-i'));
  170.         }
  171.         $item->set($value);
  172.         $item->expiresAfter($expiresInSec ?: random_int(-self::DEFAULT_TTL_GRACEself::DEFAULT_TTL_GRACE) + self::DEFAULT_TTL);
  173.         if (!$this->adapter->save($item)) {
  174.             $exception = new UnableToSaveKeyException();
  175.             $exception
  176.                 ->setKey($item->getKey())
  177.                 ->setValue($item->get());
  178.             throw $exception;
  179.         }
  180.     }
  181.     /**
  182.      * Removes a key fron the cache.
  183.      *
  184.      * @throws InvalidArgumentException
  185.      */
  186.     public function invalidate(string $entityNamestring $action, ?string $customerId null, ?string $id null): void
  187.     {
  188.         $customerId $this->getCustomerId($customerId);
  189.         $this->adapter->deleteItem($this->getKeyName($entityName$action$customerId$id));
  190.         $this->memoryCache = [];
  191.     }
  192.     /**
  193.      * Invalidate all cache using supplied tags.
  194.      *
  195.      * @throws InvalidArgumentException
  196.      */
  197.     public function invalidateTag(array $tags): bool
  198.     {
  199.         $tags array_map(function ($value) {
  200.             return self::replaceReservedCharacters($value);
  201.         }, $tags);
  202.         $this->memoryCache = [];
  203.         return $this->adapter->invalidateTags($tags);
  204.     }
  205.     public function invalidateAllKeys(): void
  206.     {
  207.         $this->memoryCache = [];
  208.         $this->adapter->clear();
  209.     }
  210.     /**
  211.      * Replaces all reserved characters with a dot.
  212.      */
  213.     public static function replaceReservedCharacters(string $key): string
  214.     {
  215.         return str_replace(str_split(ItemInterface::RESERVED_CHARACTERS1), '.'$key);
  216.     }
  217.     public function getCustomerPrefix(?string $customerId null): string
  218.     {
  219.         if (!$customerId) {
  220.             $token $this->tokenStorage->getToken();
  221.             if (null === $token || !$token instanceof TokenInterface) {
  222.                 throw new UnauthorizedHttpException('''Unable to get Token.');
  223.             }
  224.             $user $token->getUser();
  225.             if (!$user instanceof User) {
  226.                 throw new UnauthorizedHttpException('''Unable to get User.');
  227.             }
  228.         }
  229.         return sprintf('customer_%s'$customerId ?? $user->getCustomerId());
  230.     }
  231.     /**
  232.      * @throws InvalidArgumentException
  233.      */
  234.     public function invalidateCustomerIdKeys(?string $customerId): void
  235.     {
  236.         $customerId $this->getCustomerId($customerId);
  237.         $this->invalidateTag([$this->getCustomerPrefix($customerId)]);
  238.     }
  239.     /**
  240.      * @throws InvalidArgumentException
  241.      */
  242.     public function invalidateCustomerIdParamsKeys(?string $customerId): void
  243.     {
  244.         $customerId $this->getCustomerId($customerId);
  245.         $this->invalidate(StructureType::class, self::CACHE_ACTION_KEY_LIST$customerId);
  246.         $this->invalidate(StructureTypeV4::class, self::CACHE_ACTION_KEY_LIST$customerId);
  247.         $this->invalidate(Listing::class, self::CACHE_ACTION_KEY_LIST$customerId);
  248.         $this->invalidate(Civility::class, self::CACHE_ACTION_KEY_LIST$customerId);
  249.         $this->invalidate(CivilityV4::class, self::CACHE_ACTION_KEY_LIST$customerId);
  250.         $this->invalidate(LegalForm::class, self::CACHE_ACTION_KEY_LIST$customerId);
  251.         $this->invalidate(LegalFormV4::class, self::CACHE_ACTION_KEY_LIST$customerId);
  252.         $this->invalidateTag([self::replaceReservedCharacters(Quote::class)]);
  253.         $this->invalidate(Origin::class, self::CACHE_ACTION_KEY_LIST$customerId);
  254.         $this->invalidate(OriginV4::class, self::CACHE_ACTION_KEY_LIST$customerId);
  255.         $this->invalidate(Potential::class, self::CACHE_ACTION_KEY_LIST$customerId);
  256.         $this->invalidate(PotentialV4::class, self::CACHE_ACTION_KEY_LIST$customerId);
  257.         $this->invalidate(ProspectType::class, self::CACHE_ACTION_KEY_LIST$customerId);
  258.         $this->invalidate(ProspectTypeV4::class, self::CACHE_ACTION_KEY_LIST$customerId);
  259.         $this->invalidate(QuoteState::class, self::CACHE_ACTION_KEY_LIST$customerId);
  260.         $this->invalidate(TaskName::class, self::CACHE_ACTION_KEY_LIST$customerId);
  261.         $this->invalidate(TaskType::class, self::CACHE_ACTION_KEY_LIST$customerId);
  262.         $this->invalidate(TaskTypeModel::class, self::CACHE_ACTION_KEY_LIST$customerId);
  263.         $this->invalidate(TVA::class, self::CACHE_ACTION_KEY_LIST$customerId);
  264.         $this->invalidate(TVAV4::class, self::CACHE_ACTION_KEY_LIST$customerId);
  265.         $this->invalidate(Vat::class, self::CACHE_ACTION_KEY_LIST$customerId);
  266.         $this->invalidate(Warranty::class, self::CACHE_ACTION_KEY_LIST$customerId);
  267.         $this->invalidate(Unit::class, self::CACHE_ACTION_KEY_LIST$customerId);
  268.         $this->invalidate(Service::class, self::CACHE_ACTION_KEY_LIST$customerId);
  269.         $this->invalidate(ServiceV4::class, self::CACHE_ACTION_KEY_LIST$customerId);
  270.         $this->invalidate(Workforce::class, self::CACHE_ACTION_KEY_LIST$customerId);
  271.         $this->invalidate(WorkforceV4::class, self::CACHE_ACTION_KEY_LIST$customerId);
  272.         $this->invalidate(GetParamsRequest::class, self::CACHE_ACTION_KEY_LIST$customerId);
  273.         $this->invalidate(ViewOrder::class, self::CACHE_ACTION_KEY_LIST$customerId);
  274.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.QuoteStateV4::class]);
  275.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.QuoteReasonV4::class]);
  276.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.ListingV4::class]);
  277.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.Field::class]);
  278.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.Vat::class]);
  279.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.Warranty::class]);
  280.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.Unit::class]);
  281.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.StructureTypeV4::class]);
  282.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.CivilityV4::class]);
  283.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.LegalFormV4::class]);
  284.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.OriginV4::class]);
  285.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.PotentialV4::class]);
  286.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.ProspectTypeV4::class]);
  287.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.TVAV4::class]);
  288.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.ServiceV4::class]);
  289.         $this->invalidateTag([$this->getCustomerPrefix($customerId).'_'.WorkforceV4::class]);
  290.     }
  291.     private function getCustomerId(?string $customerId): ?string
  292.     {
  293.         if (!$customerId) {
  294.             $token $this->tokenStorage->getToken();
  295.             if (!$token instanceof TokenInterface) {
  296.                 throw new UnauthorizedHttpException('''Unable to get Token.');
  297.             }
  298.             $user $token->getUser();
  299.             if (!$user instanceof User) {
  300.                 throw new UnauthorizedHttpException('''Unable to get User.');
  301.             }
  302.             $customerId $user->getCustomerId();
  303.         }
  304.         return $customerId;
  305.     }
  306. }