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

moved logic into autoloaded class (performance)

parent fd789ca5
No related branches found
No related tags found
No related merge requests found
<?php declare(strict_types=1);
/**
* This file is part of assert-function-signature.
*/
namespace Grifart\AssertFunction;
final class SignatureAssertionUtil
{
private function __construct()
{
}
public static function checkSignature(callable $function, array $parameters, ?string $expectedReturnType): void
{
$reflection = new \ReflectionFunction($function);
// NUMBER OF PARAMETERS CHECK:
$numberOfParameters = $reflection->getNumberOfParameters();
if($numberOfParameters !== count($parameters)) {
throw FunctionAssertionError::wrongNumberOrArguments($reflection, count($parameters), $numberOfParameters);
}
// PARAMETER TYPES CHECK:
$i = 0;
/** @var string[] $parameters */
foreach($parameters AS $parameter) {
assert(is_string($parameter));
$parameterReflection = $reflection->getParameters()[$i++];
assert($parameterReflection instanceof \ReflectionParameter);
self::checkFunctionParameter(
$reflection,
$parameterReflection,
...self::parseType($parameter)
);
}
// RETURN TYPE:
if($expectedReturnType !== NULL) {
if(!$reflection->hasReturnType()) {
throw FunctionAssertionError::missingReturnType($reflection, $expectedReturnType);
}
if ($reflection->hasReturnType()) {
list($expectedReturnType, $expectingNullable) = self::parseType($expectedReturnType);
$returnTypeReflection = $reflection->getReturnType();
$actuallyNullable = $returnTypeReflection->allowsNull();
if ($expectingNullable !== $actuallyNullable) {
throw FunctionAssertionError::wrongReturnTypeNullability(
$reflection,
$expectingNullable,
$actuallyNullable
);
}
if ($expectedReturnType !== (string) $reflection->getReturnType()) {
throw FunctionAssertionError::wrongReturnType(
$reflection,
$expectedReturnType,
(string) $reflection->getReturnType()
);
}
}
}
}
private static function parseType(string $type): array
{
$cleanType = $type; // e.g. ?Namespace\Class
$optional = FALSE;
if ($type[0] === '?') {
$optional = TRUE;
$cleanType = substr($type, 1); // without leading '?'
}
return [$cleanType, $optional];
}
private static function checkFunctionParameter(
\ReflectionFunction $functionReflection,
\ReflectionParameter $parameterReflection,
string $expectedParameterType,
bool $expectedOptional
): void
{
$reflectionType = $parameterReflection->getType();
assert($reflectionType instanceof \ReflectionType);
// checks:
$actuallyOptional = $reflectionType->allowsNull();
if ($expectedOptional !== $actuallyOptional) {
throw FunctionAssertionError::parameterNullability(
$functionReflection,
$parameterReflection,
$expectedOptional,
$actuallyOptional
);
}
if ($expectedParameterType !== (string) $reflectionType) {
throw FunctionAssertionError::wrongParameterType(
$functionReflection,
$parameterReflection,
$expectedParameterType,
$reflectionType
);
}
}
}
......@@ -48,4 +48,44 @@ final class FunctionAssertionError extends \AssertionError {
return implode('/', $pathParts); // normalized shortened readable path
}
public static function parameterNullability(\ReflectionFunction $functionReflection, \ReflectionParameter $param, bool $expectedOptional, bool $actuallyOptional): self
{
return new self(
$functionReflection,
$expectedOptional
? "Function should have parameter {$param->getName()} optional but it is not."
: "Function should have parameter {$param->getName()} required but it is not."
);
}
public static function wrongParameterType(\ReflectionFunction $functionReflection, \ReflectionParameter $parameterReflection, string $expectedParameterType, \ReflectionType $actualParameterType): self
{
return new self(
$functionReflection,
"Wrong parameter type given in function {$parameterReflection}. {$expectedParameterType} expected. {$actualParameterType} defined in function."
);
}
public static function wrongReturnTypeNullability(\ReflectionFunction $reflectionFunction, bool $expectingNullable, bool $actuallyNullable): self
{
return new self(
$reflectionFunction,
$expectingNullable
? 'Wrong return type nullability. Expecting nullable, but was required.'
: 'Wrong return type nullability. Expecting required, but was nullable.'
);
}
public static function wrongReturnType(\ReflectionFunction $reflection, string $expectedReturnType, string $actualReturnType): self
{
return new self(
$reflection,
"Expected return type of type '$expectedReturnType', but function declares '$actualReturnType'"
);
}
};
......@@ -3,9 +3,7 @@
* 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 + throw FunctionAssertionError + catch in fn + pass it to assert -> compatible with PHP assert config
// todo: TESTS: better assertion messages + throw FunctionAssertionError + catch in fn + pass it to assert -> compatible with PHP assert config
// todo: covariance and contra-variance
namespace Grifart\AssertFunction;
......@@ -22,71 +20,16 @@ function nullable(string $classType): string
function assertSignature(callable $function, array $parameters, ?string $expectedReturnType): void
{
// todo: call assert(__impl());
$reflection = new \ReflectionFunction($function);
// NUMBER OF PARAMETERS CHECK:
$numberOfParameters = $reflection->getNumberOfParameters();
assert(
$numberOfParameters === count($parameters),
FunctionAssertionError::wrongNumberOrArguments($reflection, count($parameters), $numberOfParameters)
);
if ($numberOfParameters !== count($parameters)) {
return;
}
// PARAMETER TYPES CHECK:
$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) {
assert(
$reflection->hasReturnType(),
FunctionAssertionError::missingReturnType($reflection, $expectedReturnType)
try {
SignatureAssertionUtil::checkSignature(
$function,
$parameters,
$expectedReturnType
);
if ($reflection->hasReturnType()) {
list($_returnType, $_optional) = __parseType($expectedReturnType);
$returnTypeReflection = $reflection->getReturnType();
assert($_optional === $returnTypeReflection->allowsNull());
assert($_returnType === (string) $reflection->getReturnType());
}
}
}
function __parseType(string $type): array
{
$cleanType = $type; // e.g. ?Namespace\Class
$optional = FALSE;
if ($type[0] === '?') {
$optional = TRUE;
$cleanType = substr($type, 1); // without leading '?'
} catch (FunctionAssertionError $error) {
// todo: add callee position
assert(FALSE, $error);
}
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);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment