Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ public function handle(ServerRequestInterface $request): ResponseInterface

try {
/** @var AbstractListView<DatabaseObject, DatabaseObjectList<DatabaseObject>> $view */
$view = new $parameters['listView'](...$parameters['listViewParameters']);
$view = Helper::mapQueryParametersToClass(
$parameters['listViewParameters'],
$parameters['listView']
);
// @phpstan-ignore catch.neverThrown
} catch (\ArgumentCountError | \TypeError $e) {
if (\ENABLE_DEBUG_MODE) {
Expand Down Expand Up @@ -93,7 +96,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface
}

return new JsonResponse([
'result' => $data
'result' => $data,
]);
} else {
throw new \LogicException('Unreachable');
Expand Down
53 changes: 48 additions & 5 deletions wcfsetup/install/files/lib/http/Helper.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,61 @@ public static function getPreferredContentType(RequestInterface $request, array
*/
public static function mapQueryParameters(array $queryParameters, string $schema): mixed
{
$mapper = (new MapperBuilder())
->allowSuperfluousKeys()
->allowScalarValueCasting()
->allowUndefinedValues()
->mapper();
$mapper = self::getQueryParameterMapperBuilder()->mapper();

return $mapper->map(
$schema,
Source::array($queryParameters)
);
}

/**
* Validates query parameters against the constructor signature of the
* provided class and invokes the constructor with the mapped arguments.
* No other properties of the class are mapped.
*
* @template T of object
* @param mixed[] $queryParameters
* @param class-string<T> $className
* @return T
* @throws MappingError
*/
public static function mapQueryParametersToClass(array $queryParameters, string $className): object
{
$reflectionClass = new \ReflectionClass($className);
if (!$reflectionClass->isInstantiable()) {
throw new \InvalidArgumentException("Class '{$className}' is not instantiable.");
}

$constructor = $reflectionClass->getConstructor();
if ($constructor === null) {
return $reflectionClass->newInstance();
}

$object = $reflectionClass->newInstanceWithoutConstructor();
$constructorClosure = $constructor->getClosure($object);
\assert($constructorClosure !== null);

$arguments = self::getQueryParameterMapperBuilder()
->argumentsMapper()
->mapArguments(
$constructorClosure,
Source::array($queryParameters)
);

$constructorClosure(...$arguments);

return $object;
}

private static function getQueryParameterMapperBuilder(): MapperBuilder
{
return (new MapperBuilder())
->allowSuperfluousKeys()
->allowScalarValueCasting()
->allowUndefinedValues();
}

/**
* Validates body parameters against the provided schema. Expects
* the data source to be JSON and thus values to be of the correct
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res
throw new UserInputException('listView', 'invalid');
}

$view = new $parameters->listView(...$parameters->listViewParameters);
$view = Helper::mapQueryParametersToClass(
$parameters->listViewParameters,
$parameters->listView
);
// @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue
\assert($view instanceof AbstractListView);

Expand Down Expand Up @@ -68,5 +71,6 @@ public function __construct(
public readonly array $listViewParameters,
public readonly bool $allowInteractions = true,
public readonly bool $allowBulkInteractions = true,
) {}
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res
throw new UserInputException('listView', 'invalid');
}

$view = new $parameters->listView(...$parameters->listViewParameters);
$view = Helper::mapQueryParametersToClass(
$parameters->listViewParameters,
$parameters->listView
);
// @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue
\assert($view instanceof AbstractListView);

Expand Down Expand Up @@ -98,5 +101,6 @@ public function __construct(
public readonly bool $allowSorting = true,
public readonly bool $allowInteractions = true,
public readonly bool $allowBulkInteractions = true,
) {}
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class TaggedArticleListView extends ArticleListView
{
public function __construct(
/** @var list<int> */
/** @var non-empty-list<positive-int> */
public readonly array $tagIDs,
) {
parent::__construct();
Expand Down
Loading