Skip to content
Snippets Groups Projects
Commit 6520cf00 authored by Jan Kuchař's avatar Jan Kuchař
Browse files

prototype implementation

parent 4afc1ea4
Branches
Tags
No related merge requests found
......@@ -18,6 +18,9 @@
"psr-4": {
"Grifart\\AssertFunction\\": "src"
},
"classmap": [
"src/exceptions.php"
],
"files": [
"src/functions.php"
]
......@@ -26,7 +29,10 @@
"autoload-dev": {
"psr-4": {
"Grifart\\AssertFunction\\": "tests"
}
},
"files": [
"src/exceptions.php"
]
}
}
<?php declare(strict_types=1);
namespace Grifart\AssertFunction;
use Throwable;
final class AssertFunctionException extends \LogicException {
/** @internal use named constructors instead */
public function __construct($message, \Throwable $previous = NULL)
{
parent::__construct($message, 0, $previous);
}
private static function location(\ReflectionFunction $reflection): string
{
return $reflection->getFileName() . ':' . $reflection->getStartLine() . '-' . $reflection->getEndLine() . ' ';
}
public static function cannotStartReflection(\ReflectionException $exception): self
{
return new self('Cannot start reflection for given function.', 0, $exception);
}
public static function wrongNumberOrArguments(\ReflectionFunction $reflection, int $numberOfParameters, int $actualCount): self
{
return new self(self::location($reflection) . "Given wrong number of parameters. Expected $numberOfParameters, $actualCount given.");
}
public static function missingReturnType(\ReflectionFunction $reflection, string $type): self
{
return new self(self::location($reflection) . "Function is required to have return type of type $type.");
}
};
abstract class RuntimeException extends \RuntimeException {};
......@@ -3,6 +3,11 @@
* Used to load functions.
*/
// todo: use only assert( ... ) instead of exceptions; to be compatible without configuration with PHP env
// todo: [performance] separate code itself into autoloaded class
// todo: better assertion messages
// todo: covariance and contra-variance
namespace Grifart\AssertFunction;
function parameters(string ...$params): array
......@@ -15,7 +20,68 @@ function optional(string $classType): string
return '?' . $classType;
}
function assertFunction(callable $fn, array $parameters, string $returnType): void
function assertFunction(callable $function, array $parameters, ?string $expectedReturnType): void
{
try {
$reflection = new \ReflectionFunction($function);
} catch (\ReflectionException $exception) {
throw AssertFunctionException::cannotStartReflection($exception);
}
// PARAMETERS CHECK:
$numberOfParameters = $reflection->getNumberOfParameters();
if ($numberOfParameters !== count($parameters)) {
throw AssertFunctionException::wrongNumberOrArguments($reflection, count($parameters), $numberOfParameters);
}
$i = 0;
/** @var string[] $parameters */
foreach($parameters AS $parameter) {
assert(is_string($parameter));
$parameterReflection = $reflection->getParameters()[$i++];
assert($parameterReflection instanceof \ReflectionParameter);
__checkFunctionParameter($parameterReflection, ...__parseType($parameter)); // todo: move to helper class
}
// RETURN TYPE:
if($expectedReturnType !== NULL) {
if (!$reflection->hasReturnType()) {
throw AssertFunctionException::missingReturnType($reflection, $expectedReturnType);
}
list($_returnType, $_optional) = __parseType($expectedReturnType);
$returnTypeReflection = $reflection->getReturnType();
assert($_optional === $returnTypeReflection->allowsNull());
assert($_returnType === (string) $reflection->getReturnType());
}
}
function __parseType(string $type): array
{
$cleanType = $type;
$optional = FALSE;
if ($type[0] === '?') {
$optional = TRUE;
$cleanType = substr($type, 1); // without leading ?
}
return [$cleanType, $optional];
}
function __checkFunctionParameter(
\ReflectionParameter $parameterReflection,
string $parameterType,
bool $optional
): void
{
$reflectionType = $parameterReflection->getType();
assert($reflectionType instanceof \ReflectionType);
// checks:
assert($optional === $reflectionType->allowsNull());
assert($parameterType === (string) $reflectionType);
}
<?php declare(strict_types=1);
namespace MyTestNamespace;
require __DIR__ . '/bootstrap.php';
require __DIR__ . '/testClasses.php';
use function Grifart\AssertFunction\{assertFunction, optional, parameters};
use Tester\Assert;
$fn = function(?T1 $t1, ?T2 $t2): ?T3 {return new T3;};
assertFunction($fn, parameters(optional(T1::class), optional(T2::class)), optional(T3::class));
// missing optional check
Assert::exception(function () use ($fn) {
assertFunction($fn, parameters(T1::class, optional(T2::class)), optional(T3::class));
}, \AssertionError::class /* todo: assert message */);
Assert::exception(function () use ($fn) {
assertFunction($fn, parameters(optional(T1::class), T2::class), optional(T3::class));
}, \AssertionError::class /* todo: assert message */);
Assert::exception(function () use ($fn) {
assertFunction($fn, parameters(optional(T1::class), optional(T2::class)), T3::class);
}, \AssertionError::class /* todo: assert message */);
// Wrong type return type
Assert::exception(function () use ($fn) {
assertFunction($fn, parameters(optional(T1::class), optional(T2::class)), optional(T2::class));
}, \AssertionError::class /* todo: assert message */);
// Wrong parameter type
Assert::exception(function () use ($fn) {
assertFunction($fn, parameters(optional(T1::class), optional(T1::class)), optional(T3::class));
}, \AssertionError::class /* todo: assert message */);
<?php declare(strict_types=1);
namespace MyTestNamespace;
require __DIR__ . '/bootstrap.php';
require __DIR__ . '/testClasses.php';
use function Grifart\AssertFunction\{assertFunction, optional, parameters};
use Tester\Assert;
$f1 = function(T1 $t1, T2 $t2): T3 {return new T3;};
assertFunction($f1, parameters(T1::class, T2::class), T3::class);
// Optional by accident: parameter
Assert::exception(function () use ($f1) {
assertFunction($f1, parameters(optional(T1::class), T2::class), T3::class);
}, \AssertionError::class);
// Optional by accident: return type
Assert::exception(function () use ($f1) {
assertFunction($f1, parameters(T1::class, T2::class), optional(T3::class));
}, \AssertionError::class);
<?php declare(strict_types=1);
namespace MyTestNamespace;
require __DIR__ . '/bootstrap.php';
require __DIR__ . '/testClasses.php';
use function Grifart\AssertFunction\{assertFunction, optional, parameters};
use Tester\Assert;
$f1 = function(T1 $t1, T2 $t2): T3 {return new T3;};
// Wrong number of arguments
Assert::exception(function () use ($f1) {
assertFunction($f1, parameters(T1::class), T3::class);
}, \AssertionError::class);
Assert::exception(function () use ($f1) {
assertFunction($f1, parameters(T1::class, T2::class, T1::class), T3::class);
}, \AssertionError::class);
# http://php.net/manual/en/function.assert.php
# default
zend.assertions = 1
# required by tests
assert.exception = 1
<?php declare(strict_types=1);
namespace MyTestNamespace;
class T1 {};
class T2 {};
class T3 {};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment