From e4eb640294e8091d6a735bb02f12aef00457be30 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 2 Oct 2015 13:31:53 +0200 Subject: [PATCH] Presenter: better exception messages --- src/Application/UI/Presenter.php | 8 ++++- src/Application/UI/PresenterComponent.php | 16 +++++++-- .../UI/PresenterComponentReflection.php | 20 ++++++++--- tests/Application/Presenter.link().php7.phpt | 10 +++--- tests/Application/Presenter.link().phpt | 34 +++++++++---------- .../Application/Presenter.paramChecking.phpt | 18 +++++----- 6 files changed, 67 insertions(+), 39 deletions(-) diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index 2d22b1bca..15b8e14b2 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -1038,7 +1038,13 @@ public static function argsToParams($class, $method, & $args, $supplemental = [] $def = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : NULL; list($type, $isClass) = PresenterComponentReflection::getParameterType($param); if (!PresenterComponentReflection::convertType($args[$name], $type, $isClass)) { - throw new InvalidLinkException("Invalid value for parameter '$name' in method $class::$method(), expected " . ($type === 'NULL' ? 'scalar' : $type) . "."); + throw new InvalidLinkException(sprintf( + 'Argument $%s passed to %s() must be %s, %s given.', + $name, + $rm->getDeclaringClass()->getName() . '::' . $rm->getName(), + $type === 'NULL' ? 'scalar' : $type, + is_object($args[$name]) ? get_class($args[$name]) : gettype($args[$name]) + )); } if ($args[$name] === $def || ($def === NULL && is_scalar($args[$name]) && (string) $args[$name] === '')) { diff --git a/src/Application/UI/PresenterComponent.php b/src/Application/UI/PresenterComponent.php index dfb239c51..e63c325ea 100644 --- a/src/Application/UI/PresenterComponent.php +++ b/src/Application/UI/PresenterComponent.php @@ -126,7 +126,13 @@ public function loadState(array $params) if (isset($params[$name])) { // NULLs are ignored $type = gettype($meta['def']); if (!$reflection->convertType($params[$name], $type)) { - throw new Nette\Application\BadRequestException("Invalid value for persistent parameter '$name' in '{$this->getName()}', expected " . ($type === 'NULL' ? 'scalar' : $type) . "."); + throw new Nette\Application\BadRequestException(sprintf( + "Value passed to persistent parameter '%s' in %s must be %s, %s given.", + $name, + $this instanceof Presenter ? 'presenter ' . $this->getName() : "component '{$this->getUniqueId()}'", + $type === 'NULL' ? 'scalar' : $type, + is_object($params[$name]) ? get_class($params[$name]) : gettype($params[$name]) + )); } $this->$name = $params[$name]; } else { @@ -163,7 +169,13 @@ public function saveState(array & $params, $reflection = NULL) $type = gettype($meta['def']); if (!PresenterComponentReflection::convertType($params[$name], $type)) { - throw new InvalidLinkException(sprintf("Invalid value for persistent parameter '%s' in '%s', expected %s.", $name, $this->getName(), $type === 'NULL' ? 'scalar' : $type)); + throw new InvalidLinkException(sprintf( + "Value passed to persistent parameter '%s' in %s must be %s, %s given.", + $name, + $this instanceof Presenter ? 'presenter ' . $this->getName() : "component '{$this->getUniqueId()}'", + $type === 'NULL' ? 'scalar' : $type, + is_object($params[$name]) ? get_class($params[$name]) : gettype($params[$name]) + )); } if ($params[$name] === $meta['def'] || ($meta['def'] === NULL && is_scalar($params[$name]) && (string) $params[$name] === '')) { diff --git a/src/Application/UI/PresenterComponentReflection.php b/src/Application/UI/PresenterComponentReflection.php index 09afa1b22..494df9a2c 100644 --- a/src/Application/UI/PresenterComponentReflection.php +++ b/src/Application/UI/PresenterComponentReflection.php @@ -123,11 +123,16 @@ public static function combineArgs(\ReflectionFunctionAbstract $method, $args) if (!isset($args[$name]) && $param->isDefaultValueAvailable()) { $res[$i++] = $param->getDefaultValue(); } else { - $res[$i++] = isset($args[$name]) ? $args[$name] : NULL; + $res[$i++] = $arg = isset($args[$name]) ? $args[$name] : NULL; list($type, $isClass) = self::getParameterType($param); - if (!self::convertType($res[$i - 1], $type, $isClass)) { - $mName = $method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' . $method->getName() : $method->getName(); - throw new BadRequestException("Invalid value for parameter '$name' in method $mName(), expected " . ($type === 'NULL' ? 'scalar' : $type) . "."); + if (!self::convertType($arg, $type, $isClass)) { + throw new BadRequestException(sprintf( + 'Argument $%s passed to %s() must be %s, %s given.', + $name, + ($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName(), + $type === 'NULL' ? 'scalar' : $type, + is_object($arg) ? get_class($arg) : gettype($arg) + )); } } } @@ -206,7 +211,12 @@ public static function getParameterType(\ReflectionParameter $param) return ($ref = $param->getClass()) ? [$ref->getName(), TRUE] : [$def, FALSE]; } catch (\ReflectionException $e) { if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) { - return [$m[1], TRUE]; + throw new \LogicException(sprintf( + "Class %s not found. Check type hint of parameter $%s in %s() or 'use' statements.", + $m[1], + $param->getName(), + $param->getDeclaringFunction()->getDeclaringClass()->getName() . '::' . $param->getDeclaringFunction()->getName() + )); } throw $e; } diff --git a/tests/Application/Presenter.link().php7.phpt b/tests/Application/Presenter.link().php7.phpt index 6feb8ef99..78f6196ff 100644 --- a/tests/Application/Presenter.link().php7.phpt +++ b/tests/Application/Presenter.link().php7.phpt @@ -35,7 +35,7 @@ class TestPresenter extends Application\UI\Presenter Assert::same('/index.php?y=2&bool=1&str=1&action=default&do=hint&presenter=Test', $this->link('hint!', '1', '2', TRUE, TRUE)); Assert::same('/index.php?y=2&str=0&action=default&do=hint&presenter=Test', $this->link('hint!', '1', '2', FALSE, FALSE)); Assert::same('/index.php?action=default&do=hint&presenter=Test', $this->link('hint!', [1])); - Assert::same("#error: Invalid value for parameter 'x' in method TestPresenter::handlehint(), expected int.", $this->link('hint!', [1], (object) [1])); + Assert::same('#error: Argument $x passed to TestPresenter::handleHint() must be int, array given.', $this->link('hint!', [1], (object) [1])); Assert::same('/index.php?y=2&action=default&do=hint&presenter=Test', $this->link('hint!', [1, 'y' => 2])); Assert::same('/index.php?y=2&action=default&do=hint&presenter=Test', $this->link('hint!', ['x' => 1, 'y' => 2, 'var1' => $this->var1])); Assert::same('#error: Signal must be non-empty string.', $this->link('!')); @@ -44,11 +44,11 @@ class TestPresenter extends Application\UI\Presenter Assert::same('/index.php?sort%5By%5D%5Basc%5D=1&action=default&presenter=Test', $this->link('this', ['sort' => ['y' => ['asc' => TRUE]]])); // Presenter & signal link type checking - Assert::same("#error: Invalid value for parameter 'x' in method TestPresenter::handlehint(), expected int.", $this->link('hint!', 'x')); - Assert::same("#error: Invalid value for parameter 'bool' in method TestPresenter::handlehint(), expected bool.", $this->link('hint!', 1, 2, 3)); - Assert::same("#error: Invalid value for parameter 'x' in method TestPresenter::handlehint(), expected int.", $this->link('hint!', [[]])); + Assert::same('#error: Argument $x passed to TestPresenter::handleHint() must be int, string given.', $this->link('hint!', 'x')); + Assert::same('#error: Argument $bool passed to TestPresenter::handleHint() must be bool, integer given.', $this->link('hint!', 1, 2, 3)); + Assert::same('#error: Argument $x passed to TestPresenter::handleHint() must be int, array given.', $this->link('hint!', [[]])); Assert::same('/index.php?action=default&do=hint&presenter=Test', $this->link('hint!')); - Assert::same("#error: Invalid value for parameter 'x' in method TestPresenter::handlehint(), expected int.", $this->link('hint!', [new stdClass])); + Assert::same('#error: Argument $x passed to TestPresenter::handleHint() must be int, stdClass given.', $this->link('hint!', [new stdClass])); } diff --git a/tests/Application/Presenter.link().phpt b/tests/Application/Presenter.link().phpt index 3cbe00e7f..149989a7f 100644 --- a/tests/Application/Presenter.link().phpt +++ b/tests/Application/Presenter.link().phpt @@ -83,8 +83,8 @@ class TestPresenter extends Application\UI\Presenter Assert::same('/index.php?action=product&presenter=Test', $this->link('product', ['var1' => $this->var1])); Assert::same('/index.php?var1=20&action=product&presenter=Test', $this->link('product', ['var1' => $this->var1 * 2, 'ok' => TRUE])); Assert::same('/index.php?var1=1&ok=0&action=product&presenter=Test', $this->link('product', ['var1' => TRUE, 'ok' => '0'])); - Assert::same("#error: Invalid value for persistent parameter 'ok' in 'Test', expected boolean.", $this->link('product', ['var1' => NULL, 'ok' => 'a'])); - Assert::same("#error: Invalid value for persistent parameter 'var1' in 'Test', expected integer.", $this->link('product', ['var1' => [1], 'ok' => FALSE])); + Assert::same("#error: Value passed to persistent parameter 'ok' in presenter Test must be boolean, string given.", $this->link('product', ['var1' => NULL, 'ok' => 'a'])); + Assert::same("#error: Value passed to persistent parameter 'var1' in presenter Test must be integer, array given.", $this->link('product', ['var1' => [1], 'ok' => FALSE])); Assert::same("#error: Unable to pass parameters to action 'Test:product', missing corresponding method.", $this->link('product', 1, 2)); Assert::same('/index.php?x=1&y=2&action=product&presenter=Test', $this->link('product', ['x' => 1, 'y' => 2])); Assert::same('/index.php?action=product&presenter=Test', $this->link('product')); @@ -105,7 +105,7 @@ class TestPresenter extends Application\UI\Presenter Assert::same('/index.php?y=2&bool=1&str=1&action=default&do=buy&presenter=Test', $this->link('buy!', '1', '2', TRUE, TRUE)); Assert::same('/index.php?y=2&str=0&action=default&do=buy&presenter=Test', $this->link('buy!', '1', '2', FALSE, FALSE)); Assert::same('/index.php?action=default&do=buy&presenter=Test', $this->link('buy!', [1])); - Assert::same("#error: Invalid value for parameter 'x' in method TestPresenter::handlebuy(), expected integer.", $this->link('buy!', [1], (object) [1])); + Assert::same('#error: Argument $x passed to TestPresenter::handleBuy() must be integer, array given.', $this->link('buy!', [1], (object) [1])); Assert::same('/index.php?y=2&action=default&do=buy&presenter=Test', $this->link('buy!', [1, 'y' => 2])); Assert::same('/index.php?y=2&action=default&do=buy&presenter=Test', $this->link('buy!', ['x' => 1, 'y' => 2, 'var1' => $this->var1])); Assert::same('#error: Signal must be non-empty string.', $this->link('!')); @@ -114,19 +114,19 @@ class TestPresenter extends Application\UI\Presenter Assert::same('/index.php?sort%5By%5D%5Basc%5D=1&action=default&presenter=Test', $this->link('this', ['sort' => ['y' => ['asc' => TRUE]]])); // Presenter & signal link type checking - Assert::same("#error: Invalid value for parameter 'x' in method TestPresenter::handlebuy(), expected integer.", $this->link('buy!', 'x')); - Assert::same("#error: Invalid value for parameter 'bool' in method TestPresenter::handlebuy(), expected boolean.", $this->link('buy!', 1, 2, 3)); - Assert::same("#error: Invalid value for parameter 'x' in method TestPresenter::handlebuy(), expected integer.", $this->link('buy!', [[]])); + Assert::same('#error: Argument $x passed to TestPresenter::handleBuy() must be integer, string given.', $this->link('buy!', 'x')); + Assert::same('#error: Argument $bool passed to TestPresenter::handleBuy() must be boolean, integer given.', $this->link('buy!', 1, 2, 3)); + Assert::same('#error: Argument $x passed to TestPresenter::handleBuy() must be integer, array given.', $this->link('buy!', [[]])); Assert::same('/index.php?action=default&do=buy&presenter=Test', $this->link('buy!')); - Assert::same("#error: Invalid value for parameter 'x' in method TestPresenter::handlebuy(), expected integer.", $this->link('buy!', [new stdClass])); + Assert::same('#error: Argument $x passed to TestPresenter::handleBuy() must be integer, stdClass given.', $this->link('buy!', [new stdClass])); - Assert::same("#error: Invalid value for parameter 'a' in method TestPresenter::handleobj(), expected stdClass.", $this->link('obj!', ['x'])); + Assert::same('#error: Argument $a passed to TestPresenter::handleObj() must be stdClass, string given.', $this->link('obj!', ['x'])); Assert::same('/index.php?action=default&do=obj&presenter=Test', $this->link('obj!', [new stdClass])); - Assert::same("#error: Invalid value for parameter 'a' in method TestPresenter::handleobj(), expected stdClass.", $this->link('obj!', [new Exception])); + Assert::same('#error: Argument $a passed to TestPresenter::handleObj() must be stdClass, Exception given.', $this->link('obj!', [new Exception])); Assert::same('/index.php?action=default&do=obj&presenter=Test', $this->link('obj!', [NULL])); - Assert::same("#error: Invalid value for parameter 'b' in method TestPresenter::handleobj(), expected stdClass.", $this->link('obj!', ['b' => 'x'])); + Assert::same('#error: Argument $b passed to TestPresenter::handleObj() must be stdClass, string given.', $this->link('obj!', ['b' => 'x'])); Assert::same('/index.php?action=default&do=obj&presenter=Test', $this->link('obj!', ['b' => new stdClass])); - Assert::same("#error: Invalid value for parameter 'b' in method TestPresenter::handleobj(), expected stdClass.", $this->link('obj!', ['b' => new Exception])); + Assert::same('#error: Argument $b passed to TestPresenter::handleObj() must be stdClass, Exception given.', $this->link('obj!', ['b' => new Exception])); Assert::same('/index.php?action=default&do=obj&presenter=Test', $this->link('obj!', ['b' => NULL])); // Component link @@ -134,7 +134,7 @@ class TestPresenter extends Application\UI\Presenter Assert::same('/index.php?mycontrol-x=0&mycontrol-y=1&action=default&do=mycontrol-click&presenter=Test', $this['mycontrol']->link('click', 0, 1)); Assert::same('/index.php?mycontrol-x=0a&mycontrol-y=1a&action=default&do=mycontrol-click&presenter=Test', $this['mycontrol']->link('click', '0a', '1a')); Assert::same('/index.php?mycontrol-x=1&action=default&do=mycontrol-click&presenter=Test', $this['mycontrol']->link('click', [1])); - Assert::same("#error: Invalid value for parameter 'x' in method TestControl::handleclick(), expected scalar.", $this['mycontrol']->link('click', [1], (object) [1])); + Assert::same('#error: Argument $x passed to TestControl::handleClick() must be scalar, array given.', $this['mycontrol']->link('click', [1], (object) [1])); Assert::same('/index.php?mycontrol-x=1&action=default&do=mycontrol-click&presenter=Test', $this['mycontrol']->link('click', TRUE, FALSE)); Assert::same('/index.php?action=default&do=mycontrol-click&presenter=Test', $this['mycontrol']->link('click', NULL, '')); Assert::same('#error: Passed more parameters than method TestControl::handleClick() expects.', $this['mycontrol']->link('click', 1, 2, 3)); @@ -146,10 +146,10 @@ class TestPresenter extends Application\UI\Presenter Assert::same('http://localhost/index.php?mycontrol-x=1&mycontrol-round=1&action=default&presenter=Test#frag', $this['mycontrol']->link('//this?x=1&round=1#frag')); // Component link type checking - Assert::same("#error: Invalid value for persistent parameter 'order' in 'mycontrol', expected array.", $this['mycontrol']->link('click', ['order' => 1])); - Assert::same("#error: Invalid value for persistent parameter 'round' in 'mycontrol', expected integer.", $this['mycontrol']->link('click', ['round' => []])); + Assert::same("#error: Value passed to persistent parameter 'order' in component 'mycontrol' must be array, integer given.", $this['mycontrol']->link('click', ['order' => 1])); + Assert::same("#error: Value passed to persistent parameter 'round' in component 'mycontrol' must be integer, array given.", $this['mycontrol']->link('click', ['round' => []])); $this['mycontrol']->order = 1; - Assert::same("#error: Invalid value for persistent parameter 'order' in 'mycontrol', expected array.", $this['mycontrol']->link('click')); + Assert::same("#error: Value passed to persistent parameter 'order' in component 'mycontrol' must be array, integer given.", $this['mycontrol']->link('click')); $this['mycontrol']->order = NULL; // silent invalid link mode @@ -160,13 +160,13 @@ class TestPresenter extends Application\UI\Presenter $this->invalidLinkMode = self::INVALID_LINK_WARNING; Assert::error(function () { Assert::same('#', $this->link('product', ['var1' => NULL, 'ok' => 'a'])); - }, E_USER_WARNING, "Invalid link: Invalid value for persistent parameter 'ok' in 'Test', expected boolean."); + }, E_USER_WARNING, "Invalid link: Value passed to persistent parameter 'ok' in presenter Test must be boolean, string given."); // exception invalid link mode $this->invalidLinkMode = self::INVALID_LINK_EXCEPTION; Assert::exception(function () { $this->link('product', ['var1' => NULL, 'ok' => 'a']); - }, Nette\Application\UI\InvalidLinkException::class, "Invalid value for persistent parameter 'ok' in 'Test', expected boolean."); + }, Nette\Application\UI\InvalidLinkException::class, "Value passed to persistent parameter 'ok' in presenter Test must be boolean, string given."); $this->var1 = NULL; // NULL in persistent parameter means default Assert::same('/index.php?action=product&presenter=Test', $this->link('product')); diff --git a/tests/Application/Presenter.paramChecking.phpt b/tests/Application/Presenter.paramChecking.phpt index 67b509144..9ea26b646 100644 --- a/tests/Application/Presenter.paramChecking.phpt +++ b/tests/Application/Presenter.paramChecking.phpt @@ -48,51 +48,51 @@ Assert::exception(function () use ($presenter) { Assert::exception(function () use ($presenter) { $request = new Application\Request('Test', Http\Request::GET, ['a' => []]); $presenter->run($request); -}, Nette\Application\BadRequestException::class, "Invalid value for parameter 'a' in method TestPresenter::actionDefault(), expected scalar."); +}, Nette\Application\BadRequestException::class, 'Argument $a passed to TestPresenter::actionDefault() must be scalar, array given.'); Assert::exception(function () use ($presenter) { $request = new Application\Request('Test', Http\Request::GET, ['b' => []]); $presenter->run($request); -}, Nette\Application\BadRequestException::class, "Invalid value for parameter 'b' in method TestPresenter::actionDefault(), expected scalar."); +}, Nette\Application\BadRequestException::class, 'Argument $b passed to TestPresenter::actionDefault() must be scalar, array given.'); Assert::exception(function () use ($presenter) { $request = new Application\Request('Test', Http\Request::GET, ['c' => 1]); $presenter->run($request); -}, Nette\Application\BadRequestException::class, "Invalid value for parameter 'c' in method TestPresenter::actionDefault(), expected array."); +}, Nette\Application\BadRequestException::class, 'Argument $c passed to TestPresenter::actionDefault() must be array, integer given.'); Assert::exception(function () use ($presenter) { $request = new Application\Request('Test', Http\Request::GET, ['d' => 1]); $presenter->run($request); -}, Nette\Application\BadRequestException::class, "Invalid value for parameter 'd' in method TestPresenter::actionDefault(), expected array."); +}, Nette\Application\BadRequestException::class, 'Argument $d passed to TestPresenter::actionDefault() must be array, integer given.'); Assert::exception(function () use ($presenter) { $request = new Application\Request('Test', Http\Request::GET, ['e' => 1.1]); $presenter->run($request); -}, Nette\Application\BadRequestException::class, "Invalid value for parameter 'e' in method TestPresenter::actionDefault(), expected integer."); +}, Nette\Application\BadRequestException::class, 'Argument $e passed to TestPresenter::actionDefault() must be integer, double given.'); Assert::exception(function () use ($presenter) { $request = new Application\Request('Test', Http\Request::GET, ['e' => '1 ']); $presenter->run($request); -}, Nette\Application\BadRequestException::class, "Invalid value for parameter 'e' in method TestPresenter::actionDefault(), expected integer."); +}, Nette\Application\BadRequestException::class, 'Argument $e passed to TestPresenter::actionDefault() must be integer, string given.'); Assert::exception(function () use ($presenter) { $request = new Application\Request('Test', Http\Request::GET, ['f' => '1 ']); $presenter->run($request); -}, Nette\Application\BadRequestException::class, "Invalid value for parameter 'f' in method TestPresenter::actionDefault(), expected double."); +}, Nette\Application\BadRequestException::class, 'Argument $f passed to TestPresenter::actionDefault() must be double, string given.'); Assert::exception(function () use ($presenter) { $request = new Application\Request('Test', Http\Request::GET, ['g' => '']); $presenter->run($request); -}, Nette\Application\BadRequestException::class, "Invalid value for parameter 'g' in method TestPresenter::actionDefault(), expected boolean."); +}, Nette\Application\BadRequestException::class, 'Argument $g passed to TestPresenter::actionDefault() must be boolean, string given.'); Assert::exception(function () use ($presenter) { $request = new Application\Request('Test', Http\Request::GET, ['bool' => []]); $presenter->run($request); -}, Nette\Application\BadRequestException::class, "Invalid value for persistent parameter 'bool' in 'Test', expected boolean."); +}, Nette\Application\BadRequestException::class, "Value passed to persistent parameter 'bool' in presenter Test must be boolean, array given.");