Merge pull request #6566 from metaworx/fix/invalid-argument-exceptions

Fix: invalid argument exceptions
This commit is contained in:
Lucas Bartholemy 2023-09-15 15:15:02 +02:00 committed by GitHub
commit 47bed91f08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 432 additions and 102 deletions

View File

@ -0,0 +1,253 @@
<?php
/*
* @link https://www.humhub.org/
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\exceptions;
use Throwable;
/**
* @since 1.15
*/
trait InvalidArgumentExceptionTrait
{
protected string $methodName;
protected ?string $parameter = null;
protected array $valid = [];
protected $given;
protected string $suffix = '';
protected bool $isInstantiating = true;
/**
* @param string $parameterOrMessage Name of parameter in question, or alternatively the full message string containing at
* least one space character (ASCII 32). In this case, `$valid` and `$given` are considered to be
* `$code` and `$previous` respectively
* @param string|string[] $valid (List of) valid parameter(s)
* @param mixed $given Parameter received
* @param int $code Optional exception code
* @param Throwable|null $previous Optional previous exception
*
* @noinspection PhpDocMissingThrowsInspection
* @noinspection PhpMissingParamTypeInspection
*/
public function __construct($parameterOrMessage, $valid = null, $given = null, $code = null, $previous = null)
{
$exception = null;
$message = 'Invalid exception instantiation';
try {
if (!is_string($parameterOrMessage)) {
throw new InvalidArgumentTypeException('$parameterOrMessage', ['string'], $parameterOrMessage, 0, $this);
}
if (empty($parameterOrMessage = trim($parameterOrMessage))) {
throw new InvalidArgumentValueException('$parameterOrMessage', 'non-empty string', $parameterOrMessage, 0, $this);
}
// check if $parameter is actually the $message
if (strpos($parameterOrMessage, ' ') !== false) {
$message = $parameterOrMessage;
$code = $code ?? $valid ?? 0;
$previous = $previous ?? $given;
} else {
$trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$trace = end($trace);
$this->methodName = ltrim(($trace['class'] ?? '') . '::' . ($trace['function'] ?? 'unknown method'), ':');
$this->parameter = $parameterOrMessage;
try {
$this->setValid($valid);
} catch (InvalidArgumentTypeException $t) {
throw $t->setMethodName($this->methodName);
}
$this->given = $given;
$message = $this->formatMessage();
}
} catch (Throwable $exception) {
}
parent::__construct($message, $code, $previous);
if ($exception) {
/** @noinspection PhpUnhandledExceptionInspection */
throw $exception;
}
$this->isInstantiating = false;
}
/**
* @see static::__construct()
* @noinspection PhpUnhandledExceptionInspection
* @noinspection PhpDocMissingThrowsInspection
*/
public static function newInstance($parameterOrMessage, $valid = null, $given = null, $code = null, $previous = null): self
{
return new static($parameterOrMessage, $valid, $given, $code, $previous);
}
protected function formatPrologue(): string
{
$int = filter_var($this->parameter, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($int === null) {
return $this->parameter === null
? 'Unknown argument'
: "Argument \$" . ltrim($this->parameter, '$');
}
return 'Argument #' . $int;
}
protected function formatValid(): string
{
return (count($this->valid) > 1
? 'one of '
: '') . implode(', ', $this->valid);
}
protected function formatGiven(): string
{
$given = $this->given ?? 'NULL';
/**
* @noinspection PhpLoopNeverIteratesInspection
* @noinspection LoopWhichDoesNotLoopInspection
*/
while (empty($given)) {
if ($given === '') {
$given = 'empty string';
break;
}
if ($given === '0') {
$given = "'0'";
break;
}
if ($given === []) {
$given = '[]';
break;
}
break;
}
if (!is_string($given)) {
try {
$given = json_encode($given, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
$given = serialize($given);
}
}
return $given;
}
public function formatMessage(): string
{
return sprintf(
'%s passed to %s must be %s%s - %s given.',
$this->formatPrologue(),
$this->methodName,
$this->formatValid(),
$this->getSuffix(),
$this->formatGiven(),
);
}
protected function updateMessage(): self
{
if ($this->isInstantiating) {
return $this;
}
$this->message = $this->formatMessage();
return $this;
}
public function getGiven()
{
return $this->given;
}
public function getMethodName(): string
{
return $this->methodName;
}
public function setMethodName(string $methodName): self
{
$this->methodName = $methodName;
return $this->updateMessage();
}
public function getName(): string
{
if (method_exists(parent::class, 'getName')) {
return parent::getName() . " value";
}
return 'Invalid value';
}
public function getParameter(): ?string
{
return $this->parameter;
}
public function setParameter(?string $parameter): InvalidArgumentExceptionTrait
{
$this->parameter = $parameter;
return $this->updateMessage();
}
public function getSuffix(): string
{
return $this->suffix;
}
public function setSuffix(string $suffix): self
{
$this->suffix = $suffix;
return $this->updateMessage();
}
public function getValid(): array
{
return $this->valid;
}
public function setValid($valid): self
{
if (is_string($valid)) {
$this->valid = [$valid];
return $this;
}
if (is_iterable($valid)) {
foreach ($valid as $key => $value) {
try {
$this->valid[] = (string)($value ?? 'NULL');
} catch (\Error $t) {
throw new InvalidArgumentTypeException(sprintf("\$valid[%s]", $key), ['string'], $value, 0, $this);
}
}
return $this;
}
throw new InvalidArgumentTypeException('$valid', ['string', 'string[]'], $valid, 0, $this);
}
}

View File

@ -1,6 +1,6 @@
<?php
/**
/*
* @link https://www.humhub.org/
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
@ -8,28 +8,41 @@
namespace humhub\exceptions;
use yii\base\InvalidArgumentException;
use yii\base\InvalidArgumentException as BaseInvalidArgumentException;
/**
* @since 1.15
*/
class InvalidArgumentTypeException extends InvalidArgumentException
class InvalidArgumentTypeException extends BaseInvalidArgumentException
{
use InvalidTypeExceptionTrait;
use InvalidArgumentExceptionTrait {
getName as protected InvalidArgumentExceptionTrait_getName;
formatGiven as protected InvalidArgumentExceptionTrait_formatGiven;
formatValid as protected InvalidArgumentExceptionTrait_formatValid;
}
protected function formatPrologue(array $constructArguments): string
protected function formatValid(): string
{
$argumentName = is_array($this->parameter)
? reset($this->parameter)
: null;
$argumentNumber = is_array($this->parameter)
? key($this->parameter)
: $this->parameter;
if (empty($this->valid)) {
$this->valid = ['mixed'];
}
$argumentName = $argumentName === null
? ''
: " \$" . ltrim($argumentName, '$');
return (count($this->valid) > 1
? 'one of the following types: '
: 'of type ') . implode(', ', $this->valid);
}
return sprintf('Argument #%d%s', $argumentNumber, $argumentName);
protected function formatGiven(): string
{
return $this->given === null ? 'NULL' : get_debug_type($this->given);
}
public function getName(): string
{
if (method_exists(parent::class, 'getName')) {
return $this->InvalidArgumentExceptionTrait_getName() . " Type";
}
return 'Invalid Type';
}
}

View File

@ -0,0 +1,19 @@
<?php
/*
* @link https://www.humhub.org/
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\exceptions;
use yii\base\InvalidArgumentException as BaseInvalidArgumentException;
/**
* @since 1.15
*/
class InvalidArgumentValueException extends BaseInvalidArgumentException
{
use InvalidArgumentExceptionTrait;
}

View File

@ -1,6 +1,6 @@
<?php
/**
/*
* @link https://www.humhub.org/
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
@ -15,10 +15,35 @@ use yii\base\InvalidConfigException;
*/
class InvalidConfigTypeException extends InvalidConfigException
{
use InvalidTypeExceptionTrait;
use InvalidArgumentExceptionTrait;
protected function formatPrologue(array $constructArguments): string
protected function formatPrologue(): string
{
return "Parameter $this->parameter of configuration";
return "Parameter '$this->parameter' of configuration";
}
protected function formatValid(): string
{
if (empty($this->valid)) {
$this->valid = ['mixed'];
}
return (count($this->valid) > 1
? 'one of the following type '
: 'of type ') . implode(', ', $this->valid);
}
protected function formatGiven(): string
{
return get_debug_type($this->given);
}
public function getName(): string
{
if (method_exists(parent::class, 'getName')) {
return parent::getName() . " Type";
}
return 'Invalid Type';
}
}

View File

@ -1,75 +0,0 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\exceptions;
/**
* @since 1.15
*/
trait InvalidTypeExceptionTrait
{
// public properties
public string $methodName;
public $parameter;
public array $validType = [];
/**
* @var mixed|null
*/
public $givenValue;
/**
* @param string $method
* @param int|array $parameter = [
* int => string, // position, or [ position => name ] of the argument
* ]
* @param array|string|null $validType
* @param null $givenValue
*/
public function __construct(
$method = '',
$parameter = null,
$validType = [],
$givenValue = null,
$nullable = false,
$code = 0,
$previous = null
) {
$this->methodName = $method;
$this->parameter = $parameter;
$this->validType = (array)($validType ?? ['mixed']);
$this->givenValue = $givenValue;
if ($nullable && !in_array('null', $this->validType, true)) {
$this->validType[] = 'null';
}
$message = sprintf(
'%s passed to %s must be of type %s, %s given.',
$this->formatPrologue(func_get_args()),
$this->methodName,
implode(', ', $this->validType),
get_debug_type($this->givenValue)
);
parent::__construct($message, $code, $previous);
}
abstract protected function formatPrologue(array $constructArguments): string;
public function getName(): string
{
if (method_exists(parent::class, 'getName')) {
return parent::getName() . " Type";
}
return 'Invalid Type';
}
}

View File

@ -286,8 +286,7 @@ abstract class BaseSettingsManager extends Component
}
} elseif (!is_array($prefix)) {
throw new InvalidArgumentTypeException(
__METHOD__,
[1 => '$prefix'],
'$prefix',
['string', 'int', 'null', \Stringable::class],
$prefix
);

View File

@ -1,6 +1,6 @@
<?php
/**
/*
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
@ -8,8 +8,8 @@
namespace humhub\libs;
use humhub\exceptions\InvalidArgumentValueException;
use Yii;
use yii\base\InvalidArgumentException;
use yii\base\Exception;
/**
@ -19,7 +19,6 @@ use yii\base\Exception;
*/
class Helpers
{
/**
* Shorten a text string
*
@ -138,8 +137,9 @@ class Helpers
* Source: http://php.net/manual/en/function.ini-get.php#96996
*
* @param string $valueString
*
* @return int bytes
* @throws InvalidParamException
* @throws InvalidArgumentValueException
*/
public static function getBytesOfIniValue($valueString)
{
@ -148,7 +148,7 @@ class Helpers
}
if ($valueString === false) {
throw new InvalidArgumentException('Your configuration option of ini_get function does not exist.');
throw new InvalidArgumentValueException('Your configuration option of ini_get function does not exist.');
}
switch (substr($valueString, -1)) {
@ -263,5 +263,4 @@ class Helpers
}
}
}
}

View File

@ -0,0 +1,97 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\tests\codeception\unit\exceptions;
use Codeception\Test\Unit;
use humhub\exceptions\InvalidArgumentTypeException;
use humhub\exceptions\InvalidArgumentValueException;
use yii\base\BaseObject;
/**
* Class MimeHelperTest
*/
class InvalidArgumentExceptionTest extends Unit
{
public function testInvalidArgumentValueExceptionMessageCase1()
{
$message = 'Hello World';
$this->expectException(InvalidArgumentValueException::class);
$this->expectExceptionMessage($message);
$this->expectExceptionCode(0);
throw new InvalidArgumentValueException($message);
}
public function testInvalidArgumentValueExceptionMessageCase2()
{
$message = 'Hello World';
$this->expectException(InvalidArgumentValueException::class);
$this->expectExceptionMessage($message);
$this->expectExceptionCode(1);
throw new InvalidArgumentValueException($message, 1);
}
public function testInvalidArgumentValueExceptionParameterCase1()
{
$message = 'Argument $parameter passed to ' . __METHOD__ . ' must be bool - 3 given.';
$this->expectException(InvalidArgumentValueException::class);
$this->expectExceptionMessage($message);
$this->expectExceptionCode(0);
throw new InvalidArgumentValueException('parameter', 'bool', 3);
}
public function testInvalidArgumentValueExceptionParameterCase2()
{
$message = 'Argument $parameter passed to ' . __METHOD__ . ' must be bool - NULL given.';
$this->expectException(InvalidArgumentValueException::class);
$this->expectExceptionMessage($message);
$this->expectExceptionCode(0);
throw new InvalidArgumentValueException('parameter', 'bool');
}
public function testInvalidArgumentValueExceptionParameterCase3()
{
$message = 'Argument $parameter passed to ' . __METHOD__ . ' must be one of bool, NULL - 2 given.';
$this->expectException(InvalidArgumentValueException::class);
$this->expectExceptionMessage($message);
$this->expectExceptionCode(0);
throw new InvalidArgumentValueException('parameter', ['bool', null], 2);
}
public function testInvalidArgumentValueExceptionParameterCase4()
{
$message = 'Argument $valid passed to ' . __METHOD__ . ' must be one of the following types: string, string[] - NULL given.';
$this->expectException(InvalidArgumentTypeException::class);
$this->expectExceptionMessage($message);
$this->expectExceptionCode(0);
throw new InvalidArgumentValueException('parameter');
}
public function testInvalidArgumentValueExceptionParameterCase5()
{
$message = 'Argument $valid[1] passed to ' . __METHOD__ . ' must be of type string - yii\base\BaseObject given.';
$this->expectException(InvalidArgumentTypeException::class);
$this->expectExceptionMessage($message);
$this->expectExceptionCode(0);
throw new InvalidArgumentValueException('parameter', ['bool', new BaseObject()]);
}
}