Skip to content

Fix(Validator): Ensure assertIsType validates exception contract (GH-263) #264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 16, 2025
Merged
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
4 changes: 4 additions & 0 deletions docs/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ should be able to easily backport future new features to older versions rather e

## CHANGE LOG ##

* @dev
* [GH-263] Fixed `Validator::assertIsType()` not validating provided exception class against `InvalidTypeExceptionContract`.
* [GH-263] Added unit tests for `Validator::assertIsType()`.

* v12.0.1 (2025-04-16)
* [RB-255] Fixed `ToArrayConverter` using a new Request instance instead of the actual request.
* [BR-256] Fixed `ToArrayConverter` potentially causing fatal error if object lacks `toArray` method.
Expand Down
16 changes: 12 additions & 4 deletions src/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ public static function assertIsType(string $var_name, $value, array $allowed_typ
throw new \InvalidArgumentException('The $allowed_types array cannot be empty.');
}

// Ensure the provided exception class implements the required contract
if (!is_subclass_of($ex_class, InvalidTypeExceptionContract::class)) {
throw new \InvalidArgumentException(
sprintf('Exception class "%s" must implement "%s".', $ex_class, InvalidTypeExceptionContract::class)
);
}

// Type::EXISTING_CLASS is artificial type, so we need separate logic to handle it.
$tmp = $allowed_types;
$idx = \array_search(Type::EXISTING_CLASS, $tmp, true);
Expand All @@ -173,13 +180,14 @@ public static function assertIsType(string $var_name, $value, array $allowed_typ

if (!empty($tmp)) {
if (!\in_array($value_type, $allowed_types, true)) {
// FIXME we need to ensure $ex_class implements InvalidTypeExceptionContract at some point.
/** @var \Exception $ex_class */
/** @var InvalidTypeExceptionContract $ex_class */
throw new $ex_class($var_name, $value_type, $allowed_types);
}
} else {
// FIXME we need to ensure $ex_class implements InvalidTypeExceptionContract at some point.
throw new Ex\ClassNotFound($var_name, $value_type, $allowed_types);
// This case implies only Type::EXISTING_CLASS was allowed, but the class check failed earlier.
// We still need to throw an exception that adheres to the contract.
/** @var InvalidTypeExceptionContract $ex_class */
throw new $ex_class($var_name, $value_type, $allowed_types);
}
}

Expand Down
44 changes: 44 additions & 0 deletions tests/phpunit/Validator/ValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,48 @@ public function testAssertTypeWithVariousData(): void
}
}

/**
* Tests if assertIsType() throws InvalidArgumentException when provided
* exception class does not implement InvalidTypeExceptionContract.
*/
public function testAssertIsTypeWithInvalidExceptionClass(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessageMatches('/must implement/');

// \stdClass does not implement the required contract
Validator::assertIsType('var_name', 'string', [Type::INTEGER], \stdClass::class);
}

/**
* Tests if assertIsType() throws the specified custom exception (that implements the contract)
* when the type check fails.
*/
public function testAssertIsTypeWithValidCustomExceptionOnFailure(): void
{
// Use a valid exception class from the project that implements the contract
$customExceptionClass = Ex\NotIntegerException::class;

$this->expectException($customExceptionClass);

Validator::assertIsType('var_name', 'string', [Type::INTEGER], $customExceptionClass);
}

/**
* Tests if assertIsType() passes silently when a valid custom exception class (that implements the contract)
* is provided and the type check succeeds.
*/
public function testAssertIsTypeWithValidCustomExceptionOnSuccess(): void
{
// Use a valid exception class from the project that implements the contract
$customExceptionClass = Ex\NotIntegerException::class;

Validator::assertIsType('var_name', 123, [Type::INTEGER], $customExceptionClass);

// If no exception is thrown, the test passes.
$this->assertTrue(true);
}

// ---------------------------------------------------------------------------------------------

} // end of class