diff --git a/phpstan.neon b/phpstan.neon index ba1fee1893fbaa741ef8064a6222de216d614895..ffef0a8409a1037937cef065178ac3c79cd0f557 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -254,3 +254,8 @@ parameters: message: "#^Parameter \\#1 \\$result of method TYPO3\\\\CMS\\\\Backend\\\\Controller\\\\File\\\\FileController\\:\\:flattenResultDataValue\\(\\) expects bool\\|TYPO3\\\\CMS\\\\Core\\\\Resource\\\\File\\|TYPO3\\\\CMS\\\\Core\\\\Resource\\\\Folder, TYPO3\\\\CMS\\\\Core\\\\Resource\\\\File\\|TYPO3\\\\CMS\\\\Core\\\\Resource\\\\ProcessedFile given\\.$#" count: 1 path: typo3/sysext/backend/Classes/Controller/File/FileController.php + - + # Obsolete in v12, when either entire Request or __construct() are declared final + message: "#^Unsafe usage of new static\\(\\)\\.$#" + count: 23 + path: typo3/sysext/extbase/Classes/Mvc/Request.php diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-94428-ExtbaseRequestImplementsServerRequestInterface.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-94428-ExtbaseRequestImplementsServerRequestInterface.rst new file mode 100644 index 0000000000000000000000000000000000000000..2d366c840e60416c0d49bdf40d16dccbe29daef0 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-94428-ExtbaseRequestImplementsServerRequestInterface.rst @@ -0,0 +1,40 @@ +.. include:: ../../Includes.txt + +=================================================================== +Feature: #94428 - Extbase Request implements ServerRequestInterface +=================================================================== + +See :issue:`94428` + +Description +=========== + +The extbase :php:`TYPO3\CMS\Extbase\Mvc\Request` now implements +the PSR-7 :php:`ServerRequestInterface` and thus holds all request +related information of the main core request in addition to the +plugin namespace specific extbase arguments. + + +Impact +====== + +This allows getting information of the main request especially within +extbase controllers from :php:`$this->request`. + +Developers of fluid view helpers can now retrieve the main PSR-7 request +in many contexts from :php:`$renderingContext->getRequest()`, in addition +to the extbase specific information specified by +:php:`TYPO3\CMS\Extbase\Mvc\Request\RequestInterface`. + +Note that with future patches, the request assigned to view helper +:php:`RenderingContext` may NOT implement extbase +:php:`TYPO3\CMS\Extbase\Mvc\Request\RequestInterface` anymore, and +only PSR-7 :php:`ServerRequestInterface`. This will be the case when the +view helper is not called from within an extbase plugin, but when fluid +is started as "standalone view" in non-extbase based plugins: Often in +backend scenarios like toolbars, doc headers, non-extbase modules, etc. +Extensions should thus test for instance of extbase :php:`RequestInterface` +if they don't know the context and rely on extbase specific request data. + + +.. index:: PHP-API, ext:extbase diff --git a/typo3/sysext/extbase/Classes/Mvc/ExtbaseRequestParameters.php b/typo3/sysext/extbase/Classes/Mvc/ExtbaseRequestParameters.php new file mode 100644 index 0000000000000000000000000000000000000000..be6ab34ceecb6880e8a2a23ebbc7506089aeadcd --- /dev/null +++ b/typo3/sysext/extbase/Classes/Mvc/ExtbaseRequestParameters.php @@ -0,0 +1,483 @@ +<?php + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Extbase\Mvc; + +use TYPO3\CMS\Core\Utility\ClassNamingUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Error\Result; +use TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException; +use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException; +use TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException; +use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException; + +/** + * Extbase request related state. + * Attached as 'extbase' attribute to PSR-7 ServerRequestInterface. + * + * @internal Set up extbase internally, use TYPO3\CMS\Extbase\Mvc\Request instead. + */ +class ExtbaseRequestParameters +{ + /** + * @var string Key of the plugin which identifies the plugin. It must be a string containing [a-z0-9] + */ + protected $pluginName = ''; + + /** + * @var string Name of the extension which is supposed to handle this request. This is the extension name converted to UpperCamelCase + * + * @todo: Should probably at least init to empty string. + */ + protected $controllerExtensionName; + + /** + * @var string + * + * @todo: Should probably at least init to empty string. + */ + protected $controllerObjectName; + + /** + * @var string Object name of the controller which is supposed to handle this request. + */ + protected $controllerName = 'Standard'; + + /** + * @var string Name of the action the controller is supposed to take. + */ + protected $controllerActionName = 'index'; + + /** + * @var array The arguments for this request + */ + protected $arguments = []; + + /** + * Framework-internal arguments for this request, such as __referrer. + * All framework-internal arguments start with double underscore (__), + * and are only used from within the framework. Not for user consumption. + * Internal Arguments can be objects, in contrast to public arguments + * + * @var array + */ + protected array $internalArguments = []; + + /** + * @var string The requested representation format + */ + protected $format = 'html'; + + /** + * @var bool If this request has been changed and needs to be dispatched again + * @deprecated since v11, will be removed in v12. + */ + protected $dispatched = false; + + /** + * If this request is a forward because of an error, the original request gets filled. + * + * @var \TYPO3\CMS\Extbase\Mvc\Request|null + */ + protected $originalRequest; + + /** + * If the request is a forward because of an error, these mapping results get filled here. + * + * @var \TYPO3\CMS\Extbase\Error\Result|null + */ + protected $originalRequestMappingResults; + + /** + * Sets the dispatched flag + * + * @param bool $flag If this request has been dispatched + * @deprecated since v11, will be removed in v12. + */ + public function setDispatched($flag) + { + $this->dispatched = (bool)$flag; + } + + /** + * If this request has been dispatched and addressed by the responsible + * controller and the response is ready to be sent. + * + * The dispatcher will try to dispatch the request again if it has not been + * addressed yet. + * + * @return bool TRUE if this request has been dispatched successfully + * @deprecated since v11, will be removed in v12. + */ + public function isDispatched() + { + return $this->dispatched; + } + + /** + * @param string $controllerClassName + */ + public function __construct(string $controllerClassName = '') + { + $this->controllerObjectName = $controllerClassName; + } + + /** + * @return string + */ + public function getControllerObjectName(): string + { + return $this->controllerObjectName; + } + + /** + * Explicitly sets the object name of the controller + * + * @param string $controllerObjectName The fully qualified controller object name + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setControllerObjectName($controllerObjectName) + { + $nameParts = ClassNamingUtility::explodeObjectControllerName($controllerObjectName); + $this->controllerExtensionName = $nameParts['extensionName']; + $this->controllerName = $nameParts['controllerName']; + return $this; + } + + /** + * Sets the plugin name. + * + * @param string|null $pluginName + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setPluginName($pluginName = null) + { + if ($pluginName !== null) { + $this->pluginName = $pluginName; + } + return $this; + } + + /** + * Returns the plugin key. + * + * @return string The plugin key + */ + public function getPluginName() + { + return $this->pluginName; + } + + /** + * Sets the extension name of the controller. + * + * @param string $controllerExtensionName The extension name. + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException if the extension name is not valid + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setControllerExtensionName($controllerExtensionName): self + { + if ($controllerExtensionName !== null) { + $this->controllerExtensionName = $controllerExtensionName; + } + return $this; + } + + /** + * Returns the extension name of the specified controller. + * + * @return string The extension name + */ + public function getControllerExtensionName() + { + return $this->controllerExtensionName; + } + + /** + * Returns the extension name of the specified controller. + * + * @return string The extension key + */ + public function getControllerExtensionKey() + { + return GeneralUtility::camelCaseToLowerCaseUnderscored($this->controllerExtensionName); + } + + /** + * @var array + */ + protected $controllerAliasToClassNameMapping = []; + + /** + * @param array $controllerAliasToClassNameMapping + */ + public function setControllerAliasToClassNameMapping(array $controllerAliasToClassNameMapping) + { + // this is only needed as long as forwarded requests are altered and unless there + // is no new request object created by the request builder. + $this->controllerAliasToClassNameMapping = $controllerAliasToClassNameMapping; + return $this; + } + + /** + * Sets the name of the controller which is supposed to handle the request. + * Note: This is not the object name of the controller! + * + * @param string $controllerName Name of the controller + * @throws Exception\InvalidControllerNameException + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setControllerName($controllerName): self + { + if (!is_string($controllerName) && $controllerName !== null) { + throw new InvalidControllerNameException('The controller name must be a valid string, ' . gettype($controllerName) . ' given.', 1187176358); + } + if ($controllerName !== null) { + $this->controllerName = $controllerName; + $this->controllerObjectName = $this->controllerAliasToClassNameMapping[$controllerName] ?? ''; + // There might be no Controller Class, for example for Fluid Templates. + } + return $this; + } + + /** + * Returns the object name of the controller supposed to handle this request, if one + * was set already (if not, the name of the default controller is returned) + * + * @return string Object name of the controller + */ + public function getControllerName() + { + return $this->controllerName; + } + + /** + * Sets the name of the action contained in this request. + * + * Note that the action name must start with a lower case letter and is case sensitive. + * + * @param string $actionName Name of the action to execute by the controller + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException if the action name is not valid + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setControllerActionName($actionName): self + { + if (!is_string($actionName) && $actionName !== null) { + throw new InvalidActionNameException('The action name must be a valid string, ' . gettype($actionName) . ' given (' . $actionName . ').', 1187176359); + } + if ($actionName[0] !== strtolower($actionName[0]) && $actionName !== null) { + throw new InvalidActionNameException('The action name must start with a lower case letter, "' . $actionName . '" does not match this criteria.', 1218473352); + } + if ($actionName !== null) { + $this->controllerActionName = $actionName; + } + return $this; + } + + /** + * Returns the name of the action the controller is supposed to execute. + * + * @return string Action name + */ + public function getControllerActionName(): string + { + $controllerObjectName = $this->getControllerObjectName(); + if ($controllerObjectName !== '' && $this->controllerActionName === strtolower($this->controllerActionName)) { + // todo: this is nonsense! We can detect a non existing method in + // todo: \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin, if necessary. + // todo: At this point, we want to have a getter for a fixed value. + $actionMethodName = $this->controllerActionName . 'Action'; + $classMethods = get_class_methods($controllerObjectName); + if (is_array($classMethods)) { + foreach ($classMethods as $existingMethodName) { + if (strtolower($existingMethodName) === strtolower($actionMethodName)) { + $this->controllerActionName = substr($existingMethodName, 0, -6); + break; + } + } + } + } + return $this->controllerActionName; + } + + /** + * Sets the value of the specified argument + * + * @param string $argumentName Name of the argument to set + * @param mixed $value The new value + * @throws InvalidArgumentNameException + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setArgument(string $argumentName, $value): self + { + if ($argumentName === '') { + throw new InvalidArgumentNameException('Invalid argument name.', 1210858767); + } + if ($argumentName[0] === '_' && $argumentName[1] === '_') { + $this->internalArguments[$argumentName] = $value; + return $this; + } + if (!in_array($argumentName, ['@extension', '@subpackage', '@controller', '@action', '@format'], true)) { + $this->arguments[$argumentName] = $value; + } + return $this; + } + + /** + * Sets the whole arguments array and therefore replaces any arguments + * which existed before. + * + * @param array $arguments An array of argument names and their values + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setArguments(array $arguments): self + { + $this->arguments = []; + foreach ($arguments as $argumentName => $argumentValue) { + $this->setArgument($argumentName, $argumentValue); + } + return $this; + } + + /** + * Returns an array of arguments and their values + * + * @return array Associative array of arguments and their values (which may be arguments and values as well) + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns the value of the specified argument + * + * @param string $argumentName Name of the argument + * + * @return string|array Value of the argument + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException if such an argument does not exist + */ + public function getArgument($argumentName) + { + if (!isset($this->arguments[$argumentName])) { + throw new NoSuchArgumentException('An argument "' . $argumentName . '" does not exist for this request.', 1176558158); + } + return $this->arguments[$argumentName]; + } + + /** + * Checks if an argument of the given name exists (is set) + * + * @param string $argumentName Name of the argument to check + * + * @return bool TRUE if the argument is set, otherwise FALSE + */ + public function hasArgument($argumentName) + { + return isset($this->arguments[$argumentName]); + } + + /** + * Sets the requested representation format + * + * @param string $format The desired format, something like "html", "xml", "png", "json" or the like. Can even be something like "rss.xml". + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setFormat(string $format): self + { + $this->format = $format; + return $this; + } + + /** + * Returns the requested representation format + * + * @return string The desired format, something like "html", "xml", "png", "json" or the like. + */ + public function getFormat() + { + return $this->format; + } + + /** + * Returns the original request. Filled only if a property mapping error occurred. + * + * @return \TYPO3\CMS\Extbase\Mvc\Request|null the original request. + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function getOriginalRequest(): ?Request + { + return $this->originalRequest; + } + + /** + * @param \TYPO3\CMS\Extbase\Mvc\Request $originalRequest + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setOriginalRequest(\TYPO3\CMS\Extbase\Mvc\Request $originalRequest) + { + $this->originalRequest = $originalRequest; + } + + /** + * Get the request mapping results for the original request. + * + * @return \TYPO3\CMS\Extbase\Error\Result + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function getOriginalRequestMappingResults(): Result + { + if ($this->originalRequestMappingResults === null) { + return new Result(); + } + return $this->originalRequestMappingResults; + } + + /** + * @param \TYPO3\CMS\Extbase\Error\Result $originalRequestMappingResults + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function setOriginalRequestMappingResults(Result $originalRequestMappingResults) + { + $this->originalRequestMappingResults = $originalRequestMappingResults; + } + + /** + * Get the internal arguments of the request, i.e. every argument starting + * with two underscores. + * + * @return array + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function getInternalArguments(): array + { + return $this->internalArguments; + } + + /** + * Returns the value of the specified argument + * + * @param string $argumentName Name of the argument + * @return string|null Value of the argument, or NULL if not set. + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ + public function getInternalArgument($argumentName) + { + if (!isset($this->internalArguments[$argumentName])) { + return null; + } + return $this->internalArguments[$argumentName]; + } +} diff --git a/typo3/sysext/extbase/Classes/Mvc/Request.php b/typo3/sysext/extbase/Classes/Mvc/Request.php index ee0cadf3fa614fa45226b6d60381d643cbe17a87..18273f500e8183a496041a6056920fca272604c8 100644 --- a/typo3/sysext/extbase/Classes/Mvc/Request.php +++ b/typo3/sysext/extbase/Classes/Mvc/Request.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -15,507 +17,722 @@ namespace TYPO3\CMS\Extbase\Mvc; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; use TYPO3\CMS\Core\Http\ApplicationType; use TYPO3\CMS\Core\Http\NormalizedParams; -use TYPO3\CMS\Core\Utility\ClassNamingUtility; -use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Extbase\Error\Result; -use TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException; -use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException; -use TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException; -use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException; /** - * Represents a generic request. + * The extbase request. + * + * This is a decorator: The core PSR-7 request is hand over as constructor + * argument, this class implements ServerRequestInterface, too. + * Additionally, the extbase request details are attached as 'extbase' + * attribute to the PSR-7 request and this class implements extbase RequestInterface. + * This class has no state except the PSR-7 request, all operations are + * hand down to the PSR-7 request. */ -class Request implements RequestInterface +class Request implements ServerRequestInterface, RequestInterface { - const PATTERN_MATCH_FORMAT = '/^[a-z0-9]{1,5}$/'; + protected ServerRequestInterface $request; /** - * @var string Key of the plugin which identifies the plugin. It must be a string containing [a-z0-9] + * @todo v12: final public function __construct(ServerRequestInterface $request) */ - protected $pluginName = ''; + public function __construct($request = null) + { + if (is_string($request) && !empty($request)) { + // Deprecation layer for old extbase Request __construct(string $controllerClassName = '') + $controllerClassName = $request; + /** @var ServerRequestInterface $request */ + $request = $GLOBALS['TYPO3_REQUEST'] ?? new ServerRequest(); + $attribute = new ExtbaseRequestParameters($controllerClassName); + $request = $request->withAttribute('extbase', $attribute); + } elseif ($request === null) { + // Deprecation layer when ServerRequestInterface is not given yet + /** @var ServerRequestInterface $request */ + // Fallback "new ServerRequest()" currently used in install tool. + $request = $GLOBALS['TYPO3_REQUEST'] ?? new ServerRequest(); + $attribute = new ExtbaseRequestParameters(''); + $request = $request->withAttribute('extbase', $attribute); + } + if (!$request instanceof ServerRequestInterface) { + throw new \InvalidArgumentException( + 'Request must implement PSR-7 ServerRequestInterface', + 1624452071 + ); + } + if (!$request->getAttribute('extbase') instanceof ExtbaseRequestParameters) { + throw new \InvalidArgumentException( + 'Given request must have an attribute "extbase" of type ExtbaseAttribute', + 1624452070 + ); + } + $this->request = $request; + } /** - * @var string Name of the extension which is supposed to handle this request. This is the extension name converted to UpperCamelCase + * ExtbaseAttribute attached as attribute 'extbase' to $request carries extbase + * specific request values. This helper method type hints this attribute. */ - protected $controllerExtensionName; + protected function getExtbaseAttribute(): ExtbaseRequestParameters + { + return $this->request->getAttribute('extbase'); + } - /** - * @var string - */ - protected $controllerObjectName; + public function getServerRequest(): ServerRequestInterface + { + return $this->request; + } /** - * @var string Object name of the controller which is supposed to handle this request. + * Methods implementing extbase RequestInterface */ - protected $controllerName = 'Standard'; /** - * @var string Name of the action the controller is supposed to take. + * @inheritdoc */ - protected $controllerActionName = 'index'; + public function getControllerObjectName(): string + { + return $this->getExtbaseAttribute()->getControllerObjectName(); + } /** - * @var array The arguments for this request + * Return an instance with the specified controller object name set. */ - protected $arguments = []; + public function withControllerObjectName(string $controllerObjectName): self + { + $attribute = $this->getExtbaseAttribute()->setControllerObjectName($controllerObjectName); + $request = $this->request->withAttribute('extbase', $attribute); + return new static($request); + } /** - * Framework-internal arguments for this request, such as __referrer. - * All framework-internal arguments start with double underscore (__), - * and are only used from within the framework. Not for user consumption. - * Internal Arguments can be objects, in contrast to public arguments + * Returns the plugin key. * - * @var array - */ - protected $internalArguments = []; - - /** - * @var string The requested representation format + * @todo: Should be "public function getPluginName(): string", blocked by testing-framework */ - protected $format = 'html'; + public function getPluginName() + { + return $this->getExtbaseAttribute()->getPluginName(); + } /** - * @var bool If this request has been changed and needs to be dispatched again - * @deprecated since v11, will be removed in v12. + * Return an instance with the specified plugin name set. */ - protected $dispatched = false; + public function withPluginName($pluginName = null): self + { + $attribute = $this->getExtbaseAttribute()->setPluginName($pluginName); + $request = $this->request->withAttribute('extbase', $attribute); + return new static($request); + } /** - * If this request is a forward because of an error, the original request gets filled. + * Returns the extension name of the specified controller. * - * @var \TYPO3\CMS\Extbase\Mvc\Request|null + * @todo: Should be "public function getControllerExtensionName(): string", blocked by testing-framework */ - protected $originalRequest; + public function getControllerExtensionName() + { + return $this->getExtbaseAttribute()->getControllerExtensionName(); + } /** - * If the request is a forward because of an error, these mapping results get filled here. + * Return an instance with the specified controller extension name set. * - * @var \TYPO3\CMS\Extbase\Error\Result|null + * @param string|null $controllerExtensionName Extension name + * @return self */ - protected $originalRequestMappingResults; + public function withControllerExtensionName($controllerExtensionName): self + { + $attribute = $this->getExtbaseAttribute()->setControllerExtensionName($controllerExtensionName); + $request = $this->request->withAttribute('extbase', $attribute); + return new static($request); + } /** - * Sets the dispatched flag + * Returns the extension key of the specified controller. * - * @param bool $flag If this request has been dispatched - * @deprecated since v11, will be removed in v12. + * @return string The extension key */ - public function setDispatched($flag) + public function getControllerExtensionKey(): string { - $this->dispatched = (bool)$flag; + return $this->getExtbaseAttribute()->getControllerExtensionKey(); } /** - * If this request has been dispatched and addressed by the responsible - * controller and the response is ready to be sent. - * - * The dispatcher will try to dispatch the request again if it has not been - * addressed yet. + * Returns the controller name supposed to handle this request, if one + * was set already (if not, the name of the default controller is returned) * - * @return bool TRUE if this request has been dispatched successfully - * @deprecated since v11, will be removed in v12. + * @todo: Should be "public function getControllerName(): string", blocked by testing-framework */ - public function isDispatched() + public function getControllerName() { - return $this->dispatched; + return (string)$this->getExtbaseAttribute()->getControllerName(); } /** - * @param string $controllerClassName + * Return an instance with the specified controller name set. + * Note: This is not the object name of the controller! + * + * @param string|null $controllerName Controller name + * @return self */ - public function __construct(string $controllerClassName = '') + public function withControllerName($controllerName): self { - $this->controllerObjectName = $controllerClassName; + $attribute = $this->getExtbaseAttribute()->setControllerName($controllerName); + $request = $this->request->withAttribute('extbase', $attribute); + return new static($request); } /** - * @return string + * Returns the name of the action the controller is supposed to execute. + * + * @todo: Should be "public function getControllerActionName(): string", blocked by testing-framework */ - public function getControllerObjectName(): string + public function getControllerActionName() { - return $this->controllerObjectName; + return $this->getExtbaseAttribute()->getControllerActionName(); } /** - * Explicitly sets the object name of the controller + * Return an instance with the specified controller action name set. * - * @param string $controllerObjectName The fully qualified controller object name - * @internal only to be used within Extbase, not part of TYPO3 Core API. + * Note that the action name must start with a lower case letter and is case sensitive. + * + * @param string|null $actionName Action name + * @return self */ - public function setControllerObjectName($controllerObjectName) + public function withControllerActionName($actionName): self { - $nameParts = ClassNamingUtility::explodeObjectControllerName($controllerObjectName); - $this->controllerExtensionName = $nameParts['extensionName']; - $this->controllerName = $nameParts['controllerName']; + $attribute = $this->getExtbaseAttribute()->setControllerActionName($actionName); + $request = $this->request->withAttribute('extbase', $attribute); + return new static($request); } /** - * Sets the plugin name. - * - * @param string|null $pluginName - * @internal only to be used within Extbase, not part of TYPO3 Core API. + * @inheritdoc */ - public function setPluginName($pluginName = null) + public function getArguments(): array { - if ($pluginName !== null) { - $this->pluginName = $pluginName; - } + return $this->getExtbaseAttribute()->getArguments(); } /** - * Returns the plugin key. - * - * @return string The plugin key + * Return an instance with the specified extbase arguments, replacing + * any arguments which existed before. */ - public function getPluginName() + public function withArguments(array $arguments): self { - return $this->pluginName; + $attribute = $this->getExtbaseAttribute()->setArguments($arguments); + $request = $this->request->withAttribute('extbase', $attribute); + return new static($request); } /** - * Sets the extension name of the controller. - * - * @param string $controllerExtensionName The extension name. - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException if the extension name is not valid - * @internal only to be used within Extbase, not part of TYPO3 Core API. + * @inheritdoc */ - public function setControllerExtensionName($controllerExtensionName) + public function getArgument($argumentName) { - if ($controllerExtensionName !== null) { - $this->controllerExtensionName = $controllerExtensionName; - } + return $this->getExtbaseAttribute()->getArgument($argumentName); } /** - * Returns the extension name of the specified controller. - * - * @return string The extension name + * @inheritDoc */ - public function getControllerExtensionName() + public function hasArgument($argumentName): bool { - return $this->controllerExtensionName; + return $this->getExtbaseAttribute()->hasArgument($argumentName); } /** - * Returns the extension name of the specified controller. + * Return an instance with the specified argument set. * - * @return string The extension key + * @param string $argumentName Name of the argument to set + * @param mixed $value The new value + * @return self */ - public function getControllerExtensionKey() + public function withArgument(string $argumentName, $value): self { - return GeneralUtility::camelCaseToLowerCaseUnderscored($this->controllerExtensionName); + $attribute = $this->getExtbaseAttribute()->setArgument($argumentName, $value); + $request = $this->request->withAttribute('extbase', $attribute); + return new static($request); } /** - * @var array + * Returns the requested representation format, something + * like "html", "xml", "png", "json" or the like. */ - protected $controllerAliasToClassNameMapping = []; - - /** - * @param array $controllerAliasToClassNameMapping - */ - public function setControllerAliasToClassNameMapping(array $controllerAliasToClassNameMapping) + public function getFormat(): string { - // this is only needed as long as forwarded requests are altered and unless there - // is no new request object created by the request builder. - $this->controllerAliasToClassNameMapping = $controllerAliasToClassNameMapping; + return $this->getExtbaseAttribute()->getFormat(); } /** - * Sets the name of the controller which is supposed to handle the request. - * Note: This is not the object name of the controller! + * Return an instance with the specified derived request attribute. * - * @param string $controllerName Name of the controller - * @throws Exception\InvalidControllerNameException - * @internal only to be used within Extbase, not part of TYPO3 Core API. + * This method allows setting a single derived request attribute as + * described in getFormat(). */ - public function setControllerName($controllerName) + public function withFormat(string $format): self { - if (!is_string($controllerName) && $controllerName !== null) { - throw new InvalidControllerNameException('The controller name must be a valid string, ' . gettype($controllerName) . ' given.', 1187176358); - } - if ($controllerName !== null) { - $this->controllerName = $controllerName; - $this->controllerObjectName = $this->controllerAliasToClassNameMapping[$controllerName] ?? ''; - // There might be no Controller Class, for example for Fluid Templates. - } + $attribute = $this->getExtbaseAttribute()->setFormat($format); + $request = $this->request->withAttribute('extbase', $attribute); + return new static($request); } /** - * Returns the object name of the controller supposed to handle this request, if one - * was set already (if not, the name of the default controller is returned) - * - * @return string Object name of the controller + * Extbase @internal methods, not part of extbase RequestInterface. Should vanish as soon as unused. */ - public function getControllerName() + + /** + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. + */ + public function setControllerObjectName($controllerObjectName) { - return $this->controllerName; + $this->getExtbaseAttribute()->setControllerObjectName($controllerObjectName); } /** - * Sets the name of the action contained in this request. - * - * Note that the action name must start with a lower case letter and is case sensitive. - * - * @param string $actionName Name of the action to execute by the controller - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException if the action name is not valid - * @internal only to be used within Extbase, not part of TYPO3 Core API. + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ - public function setControllerActionName($actionName) + public function setPluginName($pluginName = null) { - if (!is_string($actionName) && $actionName !== null) { - throw new InvalidActionNameException('The action name must be a valid string, ' . gettype($actionName) . ' given (' . $actionName . ').', 1187176359); - } - if ($actionName[0] !== strtolower($actionName[0]) && $actionName !== null) { - throw new InvalidActionNameException('The action name must start with a lower case letter, "' . $actionName . '" does not match this criteria.', 1218473352); - } - if ($actionName !== null) { - $this->controllerActionName = $actionName; - } + $this->getExtbaseAttribute()->setPluginName($pluginName); } /** - * Returns the name of the action the controller is supposed to execute. - * - * @return string Action name + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ - public function getControllerActionName() + public function setControllerExtensionName($controllerExtensionName) { - $controllerObjectName = $this->getControllerObjectName(); - if ($controllerObjectName !== '' && $this->controllerActionName === strtolower($this->controllerActionName)) { - // todo: this is nonsense! We can detect a non existing method in - // todo: \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin, if necessary. - // todo: At this point, we want to have a getter for a fixed value. - $actionMethodName = $this->controllerActionName . 'Action'; - $classMethods = get_class_methods($controllerObjectName); - if (is_array($classMethods)) { - foreach ($classMethods as $existingMethodName) { - if (strtolower($existingMethodName) === strtolower($actionMethodName)) { - $this->controllerActionName = substr($existingMethodName, 0, -6); - break; - } - } - } - } - return $this->controllerActionName; + $this->getExtbaseAttribute()->setControllerExtensionName($controllerExtensionName); } /** - * Sets the value of the specified argument - * - * @param string $argumentName Name of the argument to set - * @param mixed $value The new value - * @throws Exception\InvalidArgumentNameException - * @internal only to be used within Extbase, not part of TYPO3 Core API. + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ - public function setArgument($argumentName, $value) + public function setControllerAliasToClassNameMapping(array $controllerAliasToClassNameMapping) { - if (!is_string($argumentName) || $argumentName === '') { - throw new InvalidArgumentNameException('Invalid argument name.', 1210858767); - } - if ($argumentName[0] === '_' && $argumentName[1] === '_') { - $this->internalArguments[$argumentName] = $value; - return; - } - if (!in_array($argumentName, ['@extension', '@subpackage', '@controller', '@action', '@format'], true)) { - $this->arguments[$argumentName] = $value; - } + // this is only needed as long as forwarded requests are altered and unless there + // is no new request object created by the request builder. + $this->getExtbaseAttribute()->setControllerAliasToClassNameMapping($controllerAliasToClassNameMapping); } /** - * Sets the whole arguments array and therefore replaces any arguments - * which existed before. - * - * @param array $arguments An array of argument names and their values * @internal only to be used within Extbase, not part of TYPO3 Core API. */ - public function setArguments(array $arguments) + public function withControllerAliasToClassNameMapping(array $controllerAliasToClassNameMapping): self { - $this->arguments = []; - foreach ($arguments as $argumentName => $argumentValue) { - $this->setArgument($argumentName, $argumentValue); - } + // this is only needed as long as forwarded requests are altered and unless there + // is no new request object created by the request builder. + $attribute = $this->getExtbaseAttribute()->setControllerAliasToClassNameMapping($controllerAliasToClassNameMapping); + $request = $this->request->withAttribute('extbase', $attribute); + return new static($request); } /** - * Returns an array of arguments and their values - * - * @return array Associative array of arguments and their values (which may be arguments and values as well) + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ - public function getArguments() + public function setControllerName($controllerName) { - return $this->arguments; + $this->getExtbaseAttribute()->setControllerName($controllerName); } /** - * Returns the value of the specified argument - * - * @param string $argumentName Name of the argument - * - * @return string|array Value of the argument - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException if such an argument does not exist + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ - public function getArgument($argumentName) + public function setControllerActionName($actionName) { - if (!isset($this->arguments[$argumentName])) { - throw new NoSuchArgumentException('An argument "' . $argumentName . '" does not exist for this request.', 1176558158); - } - return $this->arguments[$argumentName]; + $this->getExtbaseAttribute()->setControllerActionName($actionName); } /** - * Checks if an argument of the given name exists (is set) - * - * @param string $argumentName Name of the argument to check - * - * @return bool TRUE if the argument is set, otherwise FALSE + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ - public function hasArgument($argumentName) + public function setArgument($argumentName, $value) { - return isset($this->arguments[$argumentName]); + $this->getExtbaseAttribute()->setArgument($argumentName, $value); } /** - * Sets the requested representation format - * - * @param string $format The desired format, something like "html", "xml", "png", "json" or the like. Can even be something like "rss.xml". - * @internal only to be used within Extbase, not part of TYPO3 Core API. + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ - public function setFormat($format) + public function setArguments(array $arguments) { - $this->format = $format; + $this->getExtbaseAttribute()->setArguments($arguments); } /** - * Returns the requested representation format - * - * @return string The desired format, something like "html", "xml", "png", "json" or the like. + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ - public function getFormat() + public function setFormat($format) { - return $this->format; + $this->getExtbaseAttribute()->setFormat($format); } /** - * Returns the original request. Filled only if a property mapping error occurred. - * - * @return \TYPO3\CMS\Extbase\Mvc\Request|null the original request. * @internal only to be used within Extbase, not part of TYPO3 Core API. */ public function getOriginalRequest(): ?Request { - return $this->originalRequest; + return $this->getExtbaseAttribute()->getOriginalRequest(); } /** - * @param \TYPO3\CMS\Extbase\Mvc\Request $originalRequest - * @internal only to be used within Extbase, not part of TYPO3 Core API. + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ - public function setOriginalRequest(\TYPO3\CMS\Extbase\Mvc\Request $originalRequest) + public function setOriginalRequest(Request $originalRequest) { - $this->originalRequest = $originalRequest; + $this->getExtbaseAttribute()->setOriginalRequest($originalRequest); } /** * Get the request mapping results for the original request. * - * @return \TYPO3\CMS\Extbase\Error\Result * @internal only to be used within Extbase, not part of TYPO3 Core API. */ public function getOriginalRequestMappingResults(): Result { - if ($this->originalRequestMappingResults === null) { - return new Result(); - } - return $this->originalRequestMappingResults; + return $this->getExtbaseAttribute()->getOriginalRequestMappingResults(); } /** - * @param \TYPO3\CMS\Extbase\Error\Result $originalRequestMappingResults - * @internal only to be used within Extbase, not part of TYPO3 Core API. + * @internal only to be used within Extbase, not part of TYPO3 Core API. Violates immutability. */ public function setOriginalRequestMappingResults(Result $originalRequestMappingResults) { - $this->originalRequestMappingResults = $originalRequestMappingResults; + $this->getExtbaseAttribute()->setOriginalRequestMappingResults($originalRequestMappingResults); } /** - * Get the internal arguments of the request, i.e. every argument starting - * with two underscores. - * - * @return array * @internal only to be used within Extbase, not part of TYPO3 Core API. */ - public function getInternalArguments() + public function getInternalArguments(): array { - return $this->internalArguments; + return $this->getExtbaseAttribute()->getInternalArguments(); } /** - * Returns the value of the specified argument - * - * @param string $argumentName Name of the argument - * @return string Value of the argument, or NULL if not set. * @internal only to be used within Extbase, not part of TYPO3 Core API. */ public function getInternalArgument($argumentName) { - if (!isset($this->internalArguments[$argumentName])) { - return null; - } - return $this->internalArguments[$argumentName]; + return $this->getExtbaseAttribute()->getInternalArgument($argumentName); } /** - * Returns the name of the request method - * - * @return string Name of the request method + * Deprecated methods of extbase Request for v11 compat. + */ + + /** + * @deprecated since v11, will be removed in v12. Violates immutability. */ - public function getMethod() + public function setDispatched($flag) { - // @todo Global access is obsolete as soon as this class implements ServerRequestInterface - $request = $GLOBALS['TYPO3_REQUEST']; - return $request->getMethod(); + $this->getExtbaseAttribute()->setDispatched($flag); } /** - * Returns the request URI - * - * @return string URI of this web request - * @deprecated since v11, will be removed in v12 + * @deprecated since v11, will be removed in v12. + */ + public function isDispatched() + { + return $this->getExtbaseAttribute()->isDispatched(); + } + + /** + * @deprecated since v11, will be removed in v12. */ public function getRequestUri() { trigger_error('Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 12.0', E_USER_DEPRECATED); - - // @todo Global access is obsolete as soon as this class implements ServerRequestInterface - $mainRequest = $GLOBALS['TYPO3_REQUEST']; /** @var NormalizedParams $normalizedParams */ - $normalizedParams = $mainRequest->getAttribute('normalizedParams'); + $normalizedParams = $this->getAttribute('normalizedParams'); return $normalizedParams->getRequestUrl(); } /** - * Returns the base URI - * - * @return string Base URI of this web request - * @deprecated since v11, will be removed in v12 + * @deprecated since v11, will be removed in v12. */ public function getBaseUri() { trigger_error('Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 12.0', E_USER_DEPRECATED); - - // @todo Global access is obsolete as soon as this class implements ServerRequestInterface - $mainRequest = $GLOBALS['TYPO3_REQUEST']; /** @var NormalizedParams $normalizedParams */ - $normalizedParams = $mainRequest->getAttribute('normalizedParams'); + $normalizedParams = $this->getAttribute('normalizedParams'); $baseUri = $normalizedParams->getSiteUrl(); - if (ApplicationType::fromRequest($mainRequest)->isBackend()) { + if (ApplicationType::fromRequest($this)->isBackend()) { $baseUri .= TYPO3_mainDir; } return $baseUri; } + + /** + * Methods implementing ServerRequestInterface + */ + + /** + * @inheritdoc + */ + public function getServerParams(): array + { + return $this->request->getServerParams(); + } + + /** + * @inheritdoc + */ + public function getCookieParams(): array + { + return $this->request->getCookieParams(); + } + + /** + * @inheritdoc + */ + public function withCookieParams(array $cookies): self + { + $request = $this->request->withCookieParams($cookies); + return new static($request); + } + + /** + * @inheritdoc + */ + public function getQueryParams(): array + { + return $this->request->getQueryParams(); + } + + /** + * @inheritdoc + */ + public function withQueryParams(array $query): self + { + $request = $this->request->withQueryParams($query); + return new static($request); + } + + /** + * @inheritdoc + */ + public function getUploadedFiles(): array + { + return $this->request->getUploadedFiles(); + } + + /** + * @inheritdoc + */ + public function withUploadedFiles(array $uploadedFiles): self + { + $request = $this->request->withUploadedFiles($uploadedFiles); + return new static($request); + } + + /** + * @inheritdoc + */ + public function getParsedBody() + { + return $this->request->getParsedBody(); + } + + /** + * @inheritdoc + */ + public function withParsedBody($data): self + { + $request = $this->request->withParsedBody($data); + return new static($request); + } + + /** + * @inheritdoc + */ + public function getAttributes(): array + { + return $this->request->getAttributes(); + } + + /** + * @inheritdoc + */ + public function getAttribute($name, $default = null) + { + return $this->request->getAttribute($name, $default); + } + + /** + * @inheritdoc + */ + public function withAttribute($name, $value): self + { + $request = $this->request->withAttribute($name, $value); + return new static($request); + } + + /** + * @inheritdoc + */ + public function withoutAttribute($name): self + { + $request = $this->request->withoutAttribute($name); + return new static($request); + } + + /** + * Methods implementing RequestInterface + */ + + /** + * @inheritdoc + */ + public function getRequestTarget(): string + { + return $this->request->getRequestTarget(); + } + + /** + * @inheritdoc + */ + public function withRequestTarget($requestTarget): self + { + $request = $this->request->withRequestTarget($requestTarget); + return new static($request); + } + + /** + * @inheritdoc + */ + public function getMethod(): string + { + return $this->request->getMethod(); + } + + /** + * @inheritdoc + */ + public function withMethod($method): self + { + $request = $this->request->withMethod($method); + return new static($request); + } + + /** + * @inheritdoc + */ + public function getUri(): UriInterface + { + return $this->request->getUri(); + } + + /** + * @inheritdoc + */ + public function withUri(UriInterface $uri, $preserveHost = false): self + { + $request = $this->request->withUri($uri, $preserveHost); + return new static($request); + } + + /** + * Methods implementing MessageInterface + */ + + /** + * @inheritdoc + */ + public function getProtocolVersion(): string + { + return $this->request->getProtocolVersion(); + } + + /** + * @inheritdoc + */ + public function withProtocolVersion($version): self + { + $request = $this->request->withProtocolVersion($version); + return new static($request); + } + + /** + * @inheritdoc + */ + public function getHeaders(): array + { + return $this->request->getHeaders(); + } + + /** + * @inheritdoc + */ + public function hasHeader($name): bool + { + return $this->request->hasHeader($name); + } + + /** + * @inheritdoc + */ + public function getHeader($name): array + { + return $this->request->getHeader($name); + } + + /** + * @inheritdoc + */ + public function getHeaderLine($name): string + { + return $this->request->getHeaderLine($name); + } + + /** + * @inheritdoc + */ + public function withHeader($name, $value): self + { + $request = $this->request->withHeader($name, $value); + return new static($request); + } + + /** + * @inheritdoc + */ + public function withAddedHeader($name, $value): self + { + $request = $this->request->withAddedHeader($name, $value); + return new static($request); + } + + /** + * @inheritdoc + */ + public function withoutHeader($name): self + { + $request = $this->request->withoutHeader($name); + return new static($request); + } + + /** + * @inheritdoc + */ + public function getBody(): StreamInterface + { + return $this->request->getBody(); + } + + /** + * @inheritdoc + */ + public function withBody(StreamInterface $body): self + { + $request = $this->request->withBody($body); + return new static($request); + } } diff --git a/typo3/sysext/extbase/Classes/Mvc/RequestInterface.php b/typo3/sysext/extbase/Classes/Mvc/RequestInterface.php index b806a057c20adb32752465c72db3d8472a0d2cf7..d88b3709f9e9d2fbf561f777b99103fd859de273 100644 --- a/typo3/sysext/extbase/Classes/Mvc/RequestInterface.php +++ b/typo3/sysext/extbase/Classes/Mvc/RequestInterface.php @@ -15,77 +15,156 @@ namespace TYPO3\CMS\Extbase\Mvc; +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException; + /** - * Contract for a request. + * Contract for an extbase request. */ interface RequestInterface { /** - * Sets the dispatched flag + * Return the original PSR-7 request. * - * @param bool $flag If this request has been dispatched - * @deprecated since v11, will be removed in v12. + * @return ServerRequestInterface + * @todo v12: Enable + */ + // public function getServerRequest(): ServerRequestInterface; + + /** + * Returns the plugin key. + * @todo v12: Enable */ - public function setDispatched($flag); + // public function getPluginName(): string; /** - * If this request has been dispatched and addressed by the responsible - * controller and the response is ready to be sent. + * Return an instance with the specified plugin name set. * - * The dispatcher will try to dispatch the request again if it has not been - * addressed yet. + * @param string|null Plugin name + * @return self + * @todo v12: Enable + */ + // public function withPluginName($pluginName = null): self; + + /** + * Returns the extension name of the specified controller. * - * @return bool TRUE if this request has been dispatched successfully - * @deprecated since v11, will be removed in v12. + * @return string|null + * @todo v12: Enable */ - public function isDispatched(); + // public function getControllerExtensionName(): ?string; /** - * Returns the object name of the controller defined by the package key and - * controller name + * Return an instance with the specified controller extension name set. + * + * @param string|null Extension name + * @return self + * @todo v12: Enable + */ + // public function withControllerExtensionName($controllerExtensionName): RequestInterface; + + /** + * Returns the extension key of the specified controller. + * @todo v12: Enable + */ + // public function getControllerExtensionKey(): string; + + /** + * Returns the object name of the controller defined by the package + * key and controller name. * * @return string The controller's Object Name - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchControllerException if the controller does not exist + * @todo v12: public function getControllerObjectName(): string */ public function getControllerObjectName(); /** - * Sets the value of the specified argument - * - * @param string $argumentName Name of the argument to set - * @param mixed $value The new value + * Return an instance with the specified controller object name set. + * @todo v12: Enable + */ + // public function withControllerObjectName(string $controllerObjectName): RequestInterface; + + /** + * Return an instance with the specified controller alias + * to class name mapping set. + * @todo v12: Enable or refactor to render it obsolete. + */ + // public function withControllerAliasToClassNameMapping(array $controllerAliasToClassNameMapping): RequestInterface; + + /** + * Returns the object name of the controller supposed to handle this request, if one + * was specified already (if not, the name of the default controller is returned) + * @todo v12: Enable + */ + // public function getControllerName(): string; + + /** + * Returns the name of the action the controller is supposed to execute. + * @todo v12: Enable */ - public function setArgument($argumentName, $value); + // public function getControllerActionName(): string; /** - * Sets the whole arguments array and therefore replaces any arguments - * which existed before. + * Return an instance with the specified controller action name set. * - * @param array $arguments An array of argument names and their values + * Note that the action name must start with a lower case letter and is case sensitive. + * @param string|null Action name + * @return self + * @todo v12: Enable */ - public function setArguments(array $arguments); + //public function withControllerActionName($actionName): RequestInterface; /** - * Returns the value of the specified argument + * Returns the value of the specified argument. * * @param string $argumentName Name of the argument - * @return string Value of the argument - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException if such an argument does not exist + * @return string|array Value of the argument + * @throws NoSuchArgumentException if such an argument does not exist + * @todo v12: public function getArgument(string $argumentName) */ public function getArgument($argumentName); /** - * Checks if an argument of the given name exists (is set) - * - * @param string $argumentName Name of the argument to check - * @return bool TRUE if the argument is set, otherwise FALSE + * Checks if an argument of the given name exists (is set). + * @todo v12: public function hasArgument(string $argumentName): bool */ public function hasArgument($argumentName); /** - * Returns an array of arguments and their values + * Return an instance with the specified argument set. * - * @return array Array of arguments and their values (which may be arguments and values as well) + * @param string $argumentName Name of the argument to set + * @param mixed $value The new value + * @return RequestInterface + * @todo v12: Enable + */ + // public function withArgument(string $argumentName, $value): RequestInterface; + + /** + * Returns an array of extbase arguments and their values. + * @todo v12: public function getArguments(): array */ public function getArguments(); + + /** + * Return an instance with the specified extbase arguments, replacing + * any arguments which existed before. + * @todo v12: Enable + */ + // public function withArguments(array $arguments): RequestInterface; + + /** + * Returns the requested representation format, something + * like "html", "xml", "png", "json" or the like. + * @todo v12: Enable + */ + // public function getFormat(): string; + + /** + * Return an instance with the specified format. + * + * This method allows setting the format as described in getFormat(). + * @todo v12: Enable + */ + // public function withFormat(string $format): RequestInterface; } diff --git a/typo3/sysext/extbase/Classes/Mvc/Web/ReferringRequest.php b/typo3/sysext/extbase/Classes/Mvc/Web/ReferringRequest.php index 19d78222d88e9b11c30487e11c53d6053929f3c6..1c98d52cda7f58574148708b67efa011932cb169 100644 --- a/typo3/sysext/extbase/Classes/Mvc/Web/ReferringRequest.php +++ b/typo3/sysext/extbase/Classes/Mvc/Web/ReferringRequest.php @@ -24,14 +24,10 @@ use TYPO3\CMS\Extbase\Mvc\Request; */ class ReferringRequest extends Request { - /** - * @param string $controllerClassName - */ - public function __construct(string $controllerClassName = '') + public function __construct($request = null) { - // @todo: Move to parent::__construct() in case Request is deprecated in v11, too, otherwise drop this todo. trigger_error(__CLASS__ . ' will be removed in TYPO3 v12, use ForwardResponse instead, see ActionController->forwardToReferringRequest().', E_USER_DEPRECATED); - parent::__construct($controllerClassName); + parent::__construct($request); } /** @@ -46,16 +42,16 @@ class ReferringRequest extends Request switch ($argumentName) { case '@extension': - $this->setControllerExtensionName($value); + $this->getExtbaseAttribute()->setControllerExtensionName($value); break; case '@controller': - $this->setControllerName($value); + $this->getExtbaseAttribute()->setControllerName($value); break; case '@action': - $this->setControllerActionName($value); + $this->getExtbaseAttribute()->setControllerActionName($value); break; case '@format': - $this->setFormat($value); + $this->getExtbaseAttribute()->setFormat($value); break; } } diff --git a/typo3/sysext/extbase/Classes/Mvc/Web/RequestBuilder.php b/typo3/sysext/extbase/Classes/Mvc/Web/RequestBuilder.php index a45316c4dafc9bdc971d7484b3a968c35ba611cc..f777ffa0afe839c17860b57966df40b143534c80 100644 --- a/typo3/sysext/extbase/Classes/Mvc/Web/RequestBuilder.php +++ b/typo3/sysext/extbase/Classes/Mvc/Web/RequestBuilder.php @@ -21,11 +21,11 @@ use TYPO3\CMS\Core\Routing\PageArguments; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Mvc\Exception as MvcException; use TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException; use TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException; +use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters; use TYPO3\CMS\Extbase\Mvc\Request; use TYPO3\CMS\Extbase\Service\ExtensionService; @@ -181,21 +181,22 @@ class RequestBuilder implements SingletonInterface $controllerClassName = $this->resolveControllerClassName($parameters); $actionName = $this->resolveActionName($controllerClassName, $parameters); - $request = GeneralUtility::makeInstance(Request::class); - $request->setPluginName($this->pluginName); - $request->setControllerExtensionName($this->extensionName); - $request->setControllerAliasToClassNameMapping($this->controllerAliasToClassMapping); - $request->setControllerName($this->controllerClassToAliasMapping[$controllerClassName]); - $request->setControllerActionName($actionName); + $extbaseAttribute = new ExtbaseRequestParameters(); + $extbaseAttribute->setPluginName($this->pluginName); + $extbaseAttribute->setControllerExtensionName($this->extensionName); + $extbaseAttribute->setControllerAliasToClassNameMapping($this->controllerAliasToClassMapping); + $extbaseAttribute->setControllerName($this->controllerClassToAliasMapping[$controllerClassName]); + $extbaseAttribute->setControllerActionName($actionName); + if (isset($parameters['format']) && is_string($parameters['format']) && $parameters['format'] !== '') { - $request->setFormat(filter_var($parameters['format'], FILTER_SANITIZE_STRING)); + $extbaseAttribute->setFormat(filter_var($parameters['format'], FILTER_SANITIZE_STRING)); } else { - $request->setFormat($this->defaultFormat); + $extbaseAttribute->setFormat($this->defaultFormat); } foreach ($parameters as $argumentName => $argumentValue) { - $request->setArgument($argumentName, $argumentValue); + $extbaseAttribute->setArgument($argumentName, $argumentValue); } - return $request; + return new Request($mainRequest->withAttribute('extbase', $extbaseAttribute)); } /** diff --git a/typo3/sysext/extbase/Tests/Unit/Mvc/RequestTest.php b/typo3/sysext/extbase/Tests/Unit/Mvc/RequestTest.php index 4a57bbc49c904e94992fc228935e9f32d6971bf3..4ff550b96f181b8fd7a0de41cc66d881ef98390a 100644 --- a/typo3/sysext/extbase/Tests/Unit/Mvc/RequestTest.php +++ b/typo3/sysext/extbase/Tests/Unit/Mvc/RequestTest.php @@ -34,17 +34,6 @@ class RequestTest extends UnitTestCase self::assertEquals('theValue', $request->getArgument('someArgumentName')); } - /** - * @test - */ - public function setArgumentThrowsExceptionIfTheGivenArgumentNameIsNoString(): void - { - $this->expectException(InvalidArgumentNameException::class); - $this->expectExceptionCode(1210858767); - $request = new Request(); - $request->setArgument(123, 'theValue'); - } - /** * @test */ @@ -69,22 +58,6 @@ class RequestTest extends UnitTestCase self::assertEquals($arguments, $actualResult); } - /** - * @test - */ - public function setArgumentsCallsSetArgumentForEveryArrayEntry(): void - { - $request = $this->getMockBuilder(Request::class) - ->onlyMethods(['setArgument']) - ->getMock(); - $request->expects(self::exactly(2))->method('setArgument') - ->withConsecutive( - ['key1', 'value1'], - ['key2', 'value2'] - ); - $request->setArguments(['key1' => 'value1', 'key2' => 'value2']); - } - /** * @test */ @@ -208,11 +181,7 @@ class RequestTest extends UnitTestCase */ public function theActionNameCanBeSetAndRetrieved() { - $request = $this->getMockBuilder(Request::class) - ->onlyMethods(['getControllerObjectName']) - ->disableOriginalConstructor() - ->getMock(); - $request->expects(self::once())->method('getControllerObjectName')->willReturn(''); + $request = new Request(); $request->setControllerActionName('theAction'); self::assertEquals('theAction', $request->getControllerActionName()); }