<?php declare(strict_types=1);
namespace App\EventListener;
use App\Annotation\Activity as ActivityAnnotation;
use App\Component\HttpFoundation\Payload;
use Doctrine\Common\Annotations\Reader;
use ReflectionClass;
use ReflectionException;
use RuntimeException;
use App\Tartarus;
use Symfony\Component\HttpKernel\Event\{
ControllerEvent,
ResponseEvent,
ControllerArgumentsEvent
};
class ActivityAnnotationListener {
private $annotationReader;
protected $annotation = null;
protected $payload = null;
protected $auth = null;
public function __construct(Reader $annotationReader) {
$this->annotationReader = $annotationReader;
}
public function onKernelController(ControllerEvent $event): void {
if (!$event->isMainRequest())
return;
$controllers = $event->getController();
if (!is_array($controllers))
return;
$this->handleAnnotation($controllers);
}
public function onKernelControllerArguments(ControllerArgumentsEvent $event): void {
if (!$event->isMainRequest())
return;
foreach ($event->getArguments() as $argument) {
if ($argument instanceof Payload)
$this->payload = $argument;
elseif ($argument instanceof Tartarus\AuthInterface)
$this->auth = $argument;
}
}
/**
* @todo Improve/fix for numeric indexes and wildcard match
*/
protected function unsetKey(\stdClass &$victim, array $path) : void {
if (count($path) == 1) {
$key = current($path);
if (isset($victim->$key))
unset($victim->$key);
} else {
$key = array_shift($path);
if (isset($victim->$key))
$this->unsetKey($victim->$key, $path);
}
}
public function onKernelResponse(ResponseEvent $event) : void {
if (!$event->isMainRequest())
return;
$response = $event->getResponse();
/* Decide whether to log the activity depending on response code and annotation params */
if ($this->annotation) {
if (!$this->payload)
throw new RuntimeException('Activity requires Payload argument');
if ($this->annotation->status) {
if (!in_array($response->getStatusCode(), $this->annotation->status))
return;
}
/* Exclude data from logged payload, if applicable */
$data = $this->payload->getData();
if ($this->annotation->exclude && $data) {
foreach ($this->annotation->exclude as $path)
$this->unsetKey($data, $path);
}
/* Go ahead with logging */
$payload = json_encode($data);
$clientIp = $event->getRequest()->getClientIp() ?? '';
$userAgent = $event->getRequest()->headers->get('User-Agent') ?? '';
$action = $this->payload->getRouteName();
$created = time();
try {
$userId = ($this->auth) ? $this->auth->getUserId(['allow_locked' => true]) : 0;
} catch (Tartarus\StateError $e) {
$userId = 0;
}
/**
* TODO: Print CSV:
* user_id | created uint32 | action (routeName) | user_agent | ip_address | payload
*/
}
}
private function handleAnnotation(iterable $controllers): void {
list($controller, $method) = $controllers;
try {
$controller = new ReflectionClass($controller);
} catch (ReflectionException $e) {
throw new RuntimeException('Failed to read annotation');
}
$this->handleMethodAnnotation($controller, $method);
}
private function handleMethodAnnotation(ReflectionClass $controller, string $method): void {
$method = $controller->getMethod($method);
/* Get activity annotation */
$annotation = $this->annotationReader->getMethodAnnotation($method, ActivityAnnotation::class);
if ($annotation instanceof ActivityAnnotation) {
$this->annotation = $annotation;
}
}
}