<?php
namespace Social\CreditsBundle\Event;
use Sentry\ClientInterface;
use Symfony\Component\Cache\CacheItem;
use Doctrine\ORM\EntityManagerInterface;
use Social\CreditsBundle\Entity\CreditActionsEntity;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Social\CreditsBundle\Entity\CreditUserHistoryEntity;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Exception;
use Psr\Cache\InvalidArgumentException;
class CreditConsumerEventSubscriber implements EventSubscriberInterface
{
const INTERNAL_CACHE_LOCATION = 'social.credits.actions.definitions';
const CREDIT_CONSUMER_CHECK_ALLOWANCE = 'credit_consumer.allowance';
const CREDIT_CONSUMER_CONSUME = 'credit_consumer.consume';
const CREDIT_CONSUMER_RESTORE_LAST_CONSUMED = 'credit_consumer.restore_last_consumed';
/**
* Entity Manager
*
* @var EntityManagerInterface $entityManager
*/
private $entityManager;
/**
* Package definitions
*
* @var array $packagesDefinitions
*/
private $packagesDefinitions;
/**
* @var ClientInterface
*/
private $sentry;
/**
* CreditConsumerEventSubscriber constructor.
*
* @param EntityManagerInterface $entityManager
* @param ClientInterface $sentry
* @throws InvalidArgumentException
*/
public function __construct(
EntityManagerInterface $entityManager,
ClientInterface $sentry
) {
$this->entityManager = $entityManager;
$cache = new FilesystemAdapter();
$creditActionsDefinitions = [];
if ($cache->hasItem(self::INTERNAL_CACHE_LOCATION)) {
/** @var CacheItem $cacheItem */
$cacheItem = $cache->getItem(self::INTERNAL_CACHE_LOCATION);
$creditActionsDefinitions = json_decode($cacheItem->get(), true);
} else {
$credits = $this->entityManager
->getRepository(CreditActionsEntity::class)
->findAll();
foreach ($credits as $individualCredit) {
$creditActionsDefinitions[$individualCredit->getCode()] = [
'active' => $individualCredit->isActive(),
'name' => $individualCredit->getName(),
'code' => $individualCredit->getCode(),
'cost' => $individualCredit->getCost(),
];
}
$newCache = $cache->getItem(self::INTERNAL_CACHE_LOCATION);
$newCache->set(json_encode($creditActionsDefinitions));
$newCache->expiresAfter(36000);
$cache->save($newCache);
}
$this->packagesDefinitions = $creditActionsDefinitions;
$this->sentry = $sentry;
}
public static function getSubscribedEvents(): array
{
return [
self::CREDIT_CONSUMER_CHECK_ALLOWANCE => 'checkAvailableCredits',
self::CREDIT_CONSUMER_CONSUME => 'consumeCredits',
self::CREDIT_CONSUMER_RESTORE_LAST_CONSUMED => 'restoreLastActionConsumed',
];
}
public function checkAvailableCredits(CreditConsumerEvent $event)
{
try {
$currentUser = $event->getUser();
$this->entityManager->refresh($currentUser);
$currentAction = $event->getAction();
// If the action exists then we will see if active
if (array_key_exists($currentAction, $this->packagesDefinitions)) {
// The action is active to be used - check cost and user balance
if ($this->packagesDefinitions[$currentAction]['active'] == true) {
$cost = $this->packagesDefinitions[$currentAction]['cost'];
// Check if user has enough credits
if ($cost <= $currentUser->getCredits()) {
$event->setIsOK(true);
$event->setBillPrice($cost);
$event->setIsBilled(false);
return;
} else {
// User does not have enough to be debited from his credits
$event->setIsOK(false);
$event->setIsBilled(false);
return;
}
} else {
// Action is not active to be used - set that is ok, bill with nothing and set as paid
$event->setIsOk(true);
$event->setBillPrice(0);
$event->setIsBilled(true);
return;
}
}
// The action is not in the ones from database
$event->setIsOK(true);
$event->setBillPrice(0);
$event->setIsBilled(true);
return;
} catch (Exception $exception) {
$event->setIsOK(false);
$this->sentry->captureException($exception);
$this->sentry->captureMessage(json_encode([
'exception' => $exception->getMessage() . ' ' . $exception->getLine() . $exception->getFile(),
'Culprit' => 'Credit Consumer - check available credits',
'User' => $event->getUser(),
'Action' => $event->getAction(),
'Request' => $event->getRequest(),
]));
}
}
public function consumeCredits(CreditConsumerEvent $event)
{
try {
$currentUser = $event->getUser();
if (!$event->isBilled() && $event->isOK()) {
$currentUser->substractCredits($event->getBillPrice() > 0 ? $event->getBillPrice() : 0);
$this->entityManager->persist($currentUser);
$this->entityManager->flush();
$billingHistory = new CreditUserHistoryEntity();
$billingHistory->setUserId($currentUser);
$billingHistory->setCost($event->getBillPrice());
$billingHistory->setIsFromBonus(false);
$billingHistory->setAction($event->getAction());
$billingHistory->setType(CreditUserHistoryEntity::ACTION_TYPE_SUBTRACT);
$this->entityManager->persist($billingHistory);
$this->entityManager->flush();
$this->entityManager->refresh($currentUser);
$event->setIsBilled(true);
}
} catch (Exception $exception) {
$event->setIsOK(false);
$event->setIsBilled(false);
}
}
/** @todo - implement in case of rollback */
public function restoreLastActionConsumed()
{
}
}