Skip to content

Commit

Permalink
Presenter: better exception messages
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Oct 2, 2015
1 parent 5ef83ea commit e4eb640
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 39 deletions.
8 changes: 7 additions & 1 deletion src/Application/UI/Presenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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] === '')) {
Expand Down
16 changes: 14 additions & 2 deletions src/Application/UI/PresenterComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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] === '')) {
Expand Down
20 changes: 15 additions & 5 deletions src/Application/UI/PresenterComponentReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
));
}
}
}
Expand Down Expand Up @@ -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;
}
Expand Down
10 changes: 5 additions & 5 deletions tests/Application/Presenter.link().php7.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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('!'));
Expand All @@ -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]));
}


Expand Down
34 changes: 17 additions & 17 deletions tests/Application/Presenter.link().phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand All @@ -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('!'));
Expand All @@ -114,27 +114,27 @@ 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
Assert::same('#error: Signal must be non-empty string.', $this['mycontrol']->link('', 0, 1));
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));
Expand All @@ -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
Expand All @@ -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'));
Expand Down

0 comments on commit e4eb640

Please sign in to comment.