First Commit

This commit is contained in:
Esteban
2026-07-03 16:27:34 +02:00
commit 7a7440daab
8955 changed files with 1117958 additions and 0 deletions
@@ -0,0 +1,130 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows filtering of controller arguments.
*
* You can call getController() to retrieve the controller and getArguments
* to retrieve the current arguments. With setArguments() you can replace
* arguments that are used to call the controller.
*
* Arguments set in the event must be compatible with the signature of the
* controller.
*
* @author Christophe Coevoet <stof@notk.org>
*/
final class ControllerArgumentsEvent extends KernelEvent
{
private ControllerEvent $controllerEvent;
private array $namedArguments;
public function __construct(
HttpKernelInterface $kernel,
callable|ControllerEvent $controller,
private array $arguments,
Request $request,
?int $requestType,
) {
parent::__construct($kernel, $request, $requestType);
if (!$controller instanceof ControllerEvent) {
$controller = new ControllerEvent($kernel, $controller, $request, $requestType);
}
$this->controllerEvent = $controller;
}
public function getController(): callable
{
return $this->controllerEvent->getController();
}
/**
* @param list<object>|null $attributes
*/
public function setController(callable $controller, ?array $attributes = null): void
{
$this->controllerEvent->setController($controller, $attributes);
unset($this->namedArguments);
}
/**
* @return list<mixed>
*/
public function getArguments(): array
{
return $this->arguments;
}
/**
* @param list<mixed> $arguments
*/
public function setArguments(array $arguments): void
{
$this->arguments = $arguments;
unset($this->namedArguments);
}
/**
* @return array<string, mixed>
*/
public function getNamedArguments(): array
{
if (isset($this->namedArguments)) {
return $this->namedArguments;
}
$namedArguments = [];
$arguments = $this->arguments;
foreach ($this->controllerEvent->getControllerReflector()->getParameters() as $i => $param) {
if ($param->isVariadic()) {
$namedArguments[$param->name] = \array_slice($arguments, $i);
break;
}
if (\array_key_exists($i, $arguments)) {
$namedArguments[$param->name] = $arguments[$i];
} elseif ($param->isDefaultvalueAvailable()) {
$namedArguments[$param->name] = $param->getDefaultValue();
}
}
return $this->namedArguments = $namedArguments;
}
/**
* @template T of object
*
* @param class-string<T>|'*'|null $className
*
* @return ($className is null ? array<class-string, list<object>> : ($className is '*' ? list<object> : list<T>))
*/
public function getAttributes(?string $className = null): array
{
return $this->controllerEvent->getAttributes($className);
}
public function evaluate(mixed $value, ?ExpressionLanguage $expressionLanguage): mixed
{
if (!$value instanceof \Closure && !$value instanceof Expression) {
return $value;
}
return $this->controllerEvent->evaluate($value, $expressionLanguage, $this->getNamedArguments());
}
}
@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
/**
* Provides read-only access to controller metadata.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ControllerArgumentsMetadata extends ControllerMetadata
{
public function __construct(
ControllerEvent $controllerEvent,
private ControllerArgumentsEvent $controllerArgumentsEvent,
) {
parent::__construct($controllerEvent);
}
/**
* @return list<mixed>
*/
public function getArguments(): array
{
return $this->controllerArgumentsEvent->getArguments();
}
/**
* @return array<string, mixed>
*/
public function getNamedArguments(): array
{
return $this->controllerArgumentsEvent->getNamedArguments();
}
public function evaluate(mixed $value, ?ExpressionLanguage $expressionLanguage): mixed
{
return $this->controllerArgumentsEvent->evaluate($value, $expressionLanguage);
}
}
@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
/**
* Event dispatched for each controller attribute.
*
* @template T of object
* @template U of KernelEvent
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class ControllerAttributeEvent implements StoppableEventInterface
{
private string|array|object|null $controller;
/**
* @param T $attribute
* @param U $kernelEvent
*/
public function __construct(
/** @var T */
public readonly object $attribute,
/** @var U */
public readonly KernelEvent $kernelEvent,
private readonly ?ExpressionLanguage $expressionLanguage = null,
) {
$this->controller = match (true) {
$kernelEvent instanceof ControllerEvent => $kernelEvent->getController(),
$kernelEvent instanceof ControllerArgumentsEvent => $kernelEvent->getController(),
default => null,
};
}
public function isPropagationStopped(): bool
{
$event = $this->kernelEvent;
if ($event->isPropagationStopped()) {
return true;
}
if (!$this->controller) {
return false;
}
$controller = match (true) {
$event instanceof ControllerEvent => $event->getController(),
$event instanceof ControllerArgumentsEvent => $event->getController(),
};
return $controller instanceof \Closure ? $controller != $this->controller : $controller !== $this->controller;
}
public function evaluate(mixed $value, ?ExpressionLanguage $expressionLanguage = null): mixed
{
if (!$value instanceof \Closure && !$value instanceof Expression) {
return $value;
}
$event = $this->kernelEvent;
$expressionLanguage ??= $this->expressionLanguage;
return match (true) {
$event instanceof ControllerEvent => $event->evaluate($value, $expressionLanguage),
$event instanceof ControllerArgumentsEvent => $event->evaluate($value, $expressionLanguage),
($m = $event->controllerMetadata ?? null) instanceof ControllerMetadata => $m->evaluate($value, $expressionLanguage),
};
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows filtering of a controller callable.
*
* You can call getController() to retrieve the current controller. With
* setController() you can set a new controller that is used in the processing
* of the request.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
final class ControllerEvent extends KernelEvent
{
private string|array|object $controller;
private \ReflectionFunctionAbstract $controllerReflector;
public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, ?int $requestType)
{
parent::__construct($kernel, $request, $requestType);
$this->setController($controller);
}
public function getController(): callable
{
return $this->controller;
}
public function getControllerReflector(): \ReflectionFunctionAbstract
{
return $this->controllerReflector;
}
/**
* @param list<object>|null $attributes
*/
public function setController(callable $controller, ?array $attributes = null): void
{
if (null !== $attributes) {
if (!array_is_list($flattenAttributes = $attributes)) {
trigger_deprecation('symfony/http-kernel', '8.1', 'Passing an array of attributes grouped by class name to "%s()" is deprecated. Pass a flat list of attributes instead.', __METHOD__);
$flattenAttributes = [];
foreach ($attributes as $attributes) {
foreach (\is_array($attributes) ? $attributes : [$attributes] as $attribute) {
$flattenAttributes[] = $attribute;
}
}
}
$this->getRequest()->attributes->set('_controller_attributes', $flattenAttributes);
}
if (isset($this->controller) && ($controller instanceof \Closure ? $controller == $this->controller : $controller === $this->controller)) {
$this->controller = $controller;
return;
}
if (null === $attributes) {
$this->getRequest()->attributes->remove('_controller_attributes');
}
$this->controllerReflector = match (true) {
\is_array($controller) && method_exists(...$controller) => new \ReflectionMethod(...$controller),
\is_string($controller) && str_contains($controller, '::') => new \ReflectionMethod(...explode('::', $controller, 2)),
default => new \ReflectionFunction($controller(...)),
};
$this->controller = $controller;
}
/**
* @template T of object
*
* @param class-string<T>|'*'|null $className
*
* @return ($className is null ? array<class-string, list<object>> : ($className is '*' ? list<object> : list<T>))
*/
public function getAttributes(?string $className = null): array
{
if (null === $attributes = $this->getRequest()->attributes->get('_controller_attributes')) {
$class = match (true) {
\is_array($this->controller) && method_exists(...$this->controller) => new \ReflectionClass($this->controller[0]),
\is_string($this->controller) && false !== $i = strpos($this->controller, '::') => new \ReflectionClass(substr($this->controller, 0, $i)),
$this->controllerReflector instanceof \ReflectionFunction => $this->controllerReflector->isAnonymous() ? null : $this->controllerReflector->getClosureCalledClass(),
};
$attributes = [];
foreach (array_merge($class?->getAttributes() ?? [], $this->controllerReflector->getAttributes()) as $attribute) {
if (class_exists($attribute->getName())) {
$attributes[] = $attribute->newInstance();
}
}
$this->getRequest()->attributes->set('_controller_attributes', $attributes);
}
if ('*' === $className) {
return $attributes;
}
if (null !== $className) {
return array_values(array_filter($attributes, static fn ($attr) => $attr instanceof $className));
}
$grouped = [];
foreach ($attributes as $attribute) {
$grouped[$attribute::class][] = $attribute;
}
return $grouped;
}
public function evaluate(mixed $value, ?ExpressionLanguage $expressionLanguage, array $args = []): mixed
{
if (!$value instanceof \Closure && !$value instanceof Expression) {
return $value;
}
$controller = $this->getController();
$controller = match (true) {
\is_object($controller) && !$controller instanceof \Closure => $controller,
\is_array($controller) && \is_object($controller[0]) => $controller[0],
default => null,
};
if ($value instanceof \Closure) {
return $value($args, $this->getRequest(), $controller);
}
if (!$expressionLanguage) {
throw new \LogicException('Cannot evaluate Expression for controllers since no ExpressionLanguage service was configured.');
}
return $expressionLanguage->evaluate($value, [
'request' => $this->getRequest(),
'args' => $args,
'this' => $controller,
]);
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
/**
* Provides read-only access to controller metadata.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ControllerMetadata
{
public function __construct(
private ControllerEvent $controllerEvent,
) {
}
public function getController(): callable
{
return $this->controllerEvent->getController();
}
public function getReflector(): \ReflectionFunctionAbstract
{
return $this->controllerEvent->getControllerReflector();
}
/**
* @template T of object
*
* @param class-string<T>|'*'|null $className
*
* @return ($className is null ? array<class-string, list<object>> : ($className is '*' ? list<object> : list<T>))
*/
public function getAttributes(?string $className = null): array
{
return $this->controllerEvent->getAttributes($className);
}
public function evaluate(mixed $value, ?ExpressionLanguage $expressionLanguage): mixed
{
return $this->controllerEvent->evaluate($value, $expressionLanguage);
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows to create a response for a thrown exception.
*
* Call setResponse() to set the response that will be returned for the
* current request. The propagation of this event is stopped as soon as a
* response is set.
*
* You can also call setThrowable() to replace the thrown exception. This
* exception will be thrown if no response is set during processing of this
* event.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
final class ExceptionEvent extends RequestEvent
{
private \Throwable $throwable;
private bool $allowCustomResponseCode = false;
public function __construct(
HttpKernelInterface $kernel,
Request $request,
int $requestType,
\Throwable $e,
private bool $isKernelTerminating = false,
public readonly ?ControllerMetadata $controllerMetadata = null,
) {
parent::__construct($kernel, $request, $requestType);
$this->setThrowable($e);
}
public function getThrowable(): \Throwable
{
return $this->throwable;
}
/**
* Replaces the thrown exception.
*
* This exception will be thrown if no response is set in the event.
*/
public function setThrowable(\Throwable $exception): void
{
$this->throwable = $exception;
}
/**
* Mark the event as allowing a custom response code.
*/
public function allowCustomResponseCode(): void
{
$this->allowCustomResponseCode = true;
}
/**
* Returns true if the event allows a custom response code.
*/
public function isAllowingCustomResponseCode(): bool
{
return $this->allowCustomResponseCode;
}
public function isKernelTerminating(): bool
{
return $this->isKernelTerminating;
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Triggered whenever a request is fully processed.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
final class FinishRequestEvent extends KernelEvent
{
public function __construct(
HttpKernelInterface $kernel,
Request $request,
?int $requestType,
public readonly ?ControllerMetadata $controllerMetadata = null,
) {
parent::__construct($kernel, $request, $requestType);
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Base class for events dispatched in the HttpKernel component.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class KernelEvent extends Event
{
/**
* @param int $requestType The request type the kernel is currently processing; one of
* HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST
*/
public function __construct(
private HttpKernelInterface $kernel,
private Request $request,
private ?int $requestType,
) {
}
/**
* Returns the kernel in which this event was thrown.
*/
public function getKernel(): HttpKernelInterface
{
return $this->kernel;
}
/**
* Returns the request the kernel is currently processing.
*/
public function getRequest(): Request
{
return $this->request;
}
/**
* Returns the request type the kernel is currently processing.
*
* @return int One of HttpKernelInterface::MAIN_REQUEST and
* HttpKernelInterface::SUB_REQUEST
*/
public function getRequestType(): int
{
return $this->requestType;
}
/**
* Checks if this is the main request.
*/
public function isMainRequest(): bool
{
return HttpKernelInterface::MAIN_REQUEST === $this->requestType;
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpFoundation\Response;
/**
* Allows to create a response for a request.
*
* Call setResponse() to set the response that will be returned for the
* current request. The propagation of this event is stopped as soon as a
* response is set.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class RequestEvent extends KernelEvent
{
private ?Response $response = null;
/**
* Returns the response object.
*/
public function getResponse(): ?Response
{
return $this->response;
}
/**
* Sets a response and stops event propagation.
*/
public function setResponse(Response $response): void
{
$this->response = $response;
$this->stopPropagation();
}
/**
* Returns whether a response was set.
*
* @psalm-assert-if-true !null $this->getResponse()
*/
public function hasResponse(): bool
{
return null !== $this->response;
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows to filter a Response object.
*
* You can call getResponse() to retrieve the current response. With
* setResponse() you can set a new response that will be returned to the
* browser.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
final class ResponseEvent extends KernelEvent
{
public function __construct(
HttpKernelInterface $kernel,
Request $request,
int $requestType,
private Response $response,
public readonly ?ControllerArgumentsMetadata $controllerMetadata = null,
) {
parent::__construct($kernel, $request, $requestType);
}
public function getResponse(): Response
{
return $this->response;
}
public function setResponse(Response $response): void
{
$this->response = $response;
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows to execute logic after a response was sent.
*
* Since it's only triggered on main requests, the `getRequestType()` method
* will always return the value of `HttpKernelInterface::MAIN_REQUEST`.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
final class TerminateEvent extends KernelEvent
{
public function __construct(
HttpKernelInterface $kernel,
Request $request,
private Response $response,
) {
parent::__construct($kernel, $request, HttpKernelInterface::MAIN_REQUEST);
}
public function getResponse(): Response
{
return $this->response;
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Allows to create a response for the return value of a controller.
*
* Call setResponse() to set the response that will be returned for the
* current request. The propagation of this event is stopped as soon as a
* response is set.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
final class ViewEvent extends RequestEvent
{
public readonly ?ControllerArgumentsMetadata $controllerMetadata;
/**
* @deprecated since Symfony 8.1, use $controllerMetadata instead
*/
public private(set) ?ControllerArgumentsEvent $controllerArgumentsEvent {
get {
trigger_deprecation('symfony/http-kernel', '8.1', 'Accessing the "controllerArgumentsEvent" property of the "%s" class is deprecated. Use "controllerMetadata" instead.', __CLASS__);
if (!$m = $this->controllerMetadata) {
return null;
}
return $this->controllerArgumentsEvent ??= new ControllerArgumentsEvent($this->getKernel(), \Closure::bind(fn () => $this->controllerEvent, $m, ControllerMetadata::class)(), $m->getArguments(), $this->getRequest(), $this->getRequestType());
}
}
public function __construct(
HttpKernelInterface $kernel,
Request $request,
int $requestType,
private mixed $controllerResult,
ControllerArgumentsMetadata|ControllerArgumentsEvent|null $controllerMetadata = null,
) {
if ($controllerMetadata instanceof ControllerArgumentsEvent) {
trigger_deprecation('symfony/http-kernel', '8.1', 'Passing a ControllerArgumentsEvent to the ViewEvent constructor is deprecated. Pass a ControllerArgumentsMetadata instance instead.');
$this->controllerArgumentsEvent = $controllerMetadata;
$controllerEvent = \Closure::bind(fn () => $this->controllerEvent, $controllerMetadata, ControllerArgumentsEvent::class)();
$controllerMetadata = new ControllerArgumentsMetadata($controllerEvent, $controllerMetadata);
}
$this->controllerMetadata = $controllerMetadata;
parent::__construct($kernel, $request, $requestType);
}
public function getControllerResult(): mixed
{
return $this->controllerResult;
}
public function setControllerResult(mixed $controllerResult): void
{
$this->controllerResult = $controllerResult;
}
}