<?php
/*
* This file is part of the APYBreadcrumbTrailBundle.
*
* (c) Abhoryo <abhoryo@free.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace APY\BreadcrumbTrailBundle\EventListener;
use APY\BreadcrumbTrailBundle\Annotation\Breadcrumb;
use APY\BreadcrumbTrailBundle\Annotation\ResetBreadcrumbTrail;
use APY\BreadcrumbTrailBundle\BreadcrumbTrail\Trail;
use APY\BreadcrumbTrailBundle\MixedAnnotationWithAttributeBreadcrumbsException;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class BreadcrumbListener
{
/**
* @var Reader An Reader instance
*/
protected $reader;
/**
* @var Trail An Trail instance
*/
protected $breadcrumbTrail;
private $supportedAttributes = [
Breadcrumb::class,
ResetBreadcrumbTrail::class,
];
/**
* Constructor.
*
* @param Reader $reader An Reader instance
* @param Trail $breadcrumbTrail An Trail instance
*/
public function __construct(Reader $reader, Trail $breadcrumbTrail)
{
$this->reader = $reader;
$this->breadcrumbTrail = $breadcrumbTrail;
}
/**
* @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent|\Symfony\Component\HttpKernel\Event\ControllerEvent $event
*/
public function onKernelController(KernelEvent $event)
{
$controller = $event->getController();
$reflectableClass = \is_array($controller) ? $controller[0] : \get_class($controller);
$reflectableMethod = \is_array($controller) ? $controller[1] : '__invoke';
// Annotations from class
$class = new \ReflectionClass($reflectableClass);
// Manage JMSSecurityExtraBundle proxy class
if (false !== $className = $this->getRealClass($class->getName())) {
$class = new \ReflectionClass($className);
}
if ($class->isAbstract()) {
throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class));
}
if (HttpKernelInterface::MASTER_REQUEST == $event->getRequestType()) {
$this->breadcrumbTrail->reset();
// Annotations from class
$classBreadcrumbs = $this->reader->getClassAnnotations($class);
if ($this->supportsLoadingAttributes()) {
$classAttributeBreadcrumbs = $this->getAttributes($class);
if (\count($classBreadcrumbs) > 0) {
trigger_deprecation('apy/breadcrumb-bundle', '1.7', 'Please replace the annotations in "%s" with attributes. Adding Breadcrumbs via annotations is deprecated and will be removed in v2.0, but luckily your platform supports using Attributes.', $class->name);
}
if (\count($classAttributeBreadcrumbs) > 0) {
if (\count($classBreadcrumbs) > 0) {
throw MixedAnnotationWithAttributeBreadcrumbsException::forClass($class->name);
}
$classBreadcrumbs = $classAttributeBreadcrumbs;
}
}
$this->addBreadcrumbsToTrail($classBreadcrumbs);
// Annotations from method
$method = $class->getMethod($reflectableMethod);
$methodBreadcrumbs = $this->reader->getMethodAnnotations($method);
if ($this->supportsLoadingAttributes()) {
$methodAttributeBreadcrumbs = $this->getAttributes($method);
if (\count($methodBreadcrumbs) > 0) {
trigger_deprecation('apy/breadcrumb-bundle', '1.7', 'Please replace the annotations in "%s" with attributes. Adding Breadcrumbs via annotations is deprecated and will be removed in v2.0, but luckily your platform supports using Attributes.', $class->name.'::'.$method->name);
}
if (\count($methodAttributeBreadcrumbs) > 0) {
if (\count($methodBreadcrumbs) > 0) {
throw MixedAnnotationWithAttributeBreadcrumbsException::forClassMethod($class->name, $method->name);
}
$methodBreadcrumbs = $methodAttributeBreadcrumbs;
}
}
$this->addBreadcrumbsToTrail($methodBreadcrumbs);
}
}
/**
* @param array $annotations Array of Breadcrumb annotations
*/
private function addBreadcrumbsToTrail(array $annotations)
{
// requirements (@Breadcrumb)
foreach ($annotations as $annotation) {
if ($annotation instanceof ResetBreadcrumbTrail) {
$this->breadcrumbTrail->reset();
continue;
}
if ($annotation instanceof Breadcrumb) {
$template = $annotation->getTemplate();
$title = $annotation->getTitle();
if (null === $title) {
trigger_deprecation('apy/breadcrumb-bundle', '1.8', 'Resetting the breadcrumb trail by passing a Breadcrumb without parameters, and will throw an exception in v2.0. Use #[ResetBreadcrumbTrail] attribute instead.');
}
if (null != $template) {
$this->breadcrumbTrail->setTemplate($template);
if (null === $title) {
continue;
}
}
$this->breadcrumbTrail->add(
$title,
$annotation->getRouteName(),
$annotation->getRouteParameters(),
$annotation->getRouteAbsolute(),
$annotation->getPosition(),
$annotation->getAttributes()
);
}
}
}
private function getRealClass($className)
{
if (false === $pos = strrpos($className, '\\__CG__\\')) {
return false;
}
return substr($className, $pos + 8);
}
private function supportsLoadingAttributes(): bool
{
return \PHP_VERSION_ID >= 80000;
}
/**
* @param \ReflectionClass|\ReflectionMethod $reflected
*
* @return array<Breadcrumb>
*/
private function getAttributes($reflected): array
{
if (false === $this->supportsLoadingAttributes()) {
throw new \RuntimeException('Detected an attempt on getting attributes while your version of PHP does not support this.');
}
$attributes = [];
foreach ($reflected->getAttributes() as $reflectionAttribute) {
if (false === \in_array($reflectionAttribute->getName(), $this->supportedAttributes)) {
continue;
}
$attributes[] = $reflectionAttribute->newInstance();
}
return $attributes;
}
}