Skip to content

Commit

Permalink
Report an error instead of a failure when the prerequisites for using…
Browse files Browse the repository at this point in the history
… the custom comparison function are not met
  • Loading branch information
sebastianbergmann committed Sep 28, 2020
1 parent 6f084fa commit 6099c5e
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 125 deletions.
160 changes: 53 additions & 107 deletions src/Framework/Constraint/Object/ObjectEquals.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@

use function get_class;
use function is_object;
use PHPUnit\Framework\ActualValueIsNotAnObjectException;
use PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException;
use PHPUnit\Framework\ComparisonMethodDoesNotDeclareBoolReturnTypeException;
use PHPUnit\Framework\ComparisonMethodDoesNotDeclareExactlyOneParameterException;
use PHPUnit\Framework\ComparisonMethodDoesNotDeclareParameterTypeException;
use PHPUnit\Framework\ComparisonMethodDoesNotExistException;
use ReflectionNamedType;
use ReflectionObject;

Expand All @@ -19,20 +25,6 @@
*/
final class ObjectEquals extends Constraint
{
private const ACTUAL_IS_NOT_AN_OBJECT = 1;

private const ACTUAL_DOES_NOT_HAVE_METHOD = 2;

private const METHOD_DOES_NOT_HAVE_BOOL_RETURN_TYPE = 3;

private const METHOD_DOES_NOT_ACCEPT_EXACTLY_ONE_ARGUMENT = 4;

private const PARAMETER_DOES_NOT_HAVE_DECLARED_TYPE = 5;

private const EXPECTED_NOT_COMPATIBLE_WITH_PARAMETER_TYPE = 6;

private const OBJECTS_ARE_NOT_EQUAL_ACCORDING_TO_METHOD = 7;

/**
* @var object
*/
Expand All @@ -43,11 +35,6 @@ final class ObjectEquals extends Constraint
*/
private $method;

/**
* @var int
*/
private $failureReason;

public function __construct(object $object, string $method = 'equals')
{
$this->expected = $object;
Expand All @@ -59,71 +46,85 @@ public function toString(): string
return 'two objects are equal';
}

/**
* @throws ActualValueIsNotAnObjectException
* @throws ComparisonMethodDoesNotExistException
* @throws ComparisonMethodDoesNotDeclareBoolReturnTypeException
* @throws ComparisonMethodDoesNotDeclareExactlyOneParameterException
* @throws ComparisonMethodDoesNotDeclareParameterTypeException
* @throws ComparisonMethodDoesNotAcceptParameterTypeException
*/
protected function matches($other): bool
{
if (!is_object($other)) {
$this->failureReason = self::ACTUAL_IS_NOT_AN_OBJECT;

return false;
throw new ActualValueIsNotAnObjectException;
}

$object = new ReflectionObject($other);

if (!$object->hasMethod($this->method)) {
$this->failureReason = self::ACTUAL_DOES_NOT_HAVE_METHOD;

return false;
throw new ComparisonMethodDoesNotExistException(
get_class($other),
$this->method
);
}

/** @noinspection PhpUnhandledExceptionInspection */
$method = $object->getMethod($this->method);

if (!$method->hasReturnType()) {
$this->failureReason = self::METHOD_DOES_NOT_HAVE_BOOL_RETURN_TYPE;

return false;
throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(
get_class($other),
$this->method
);
}

$returnType = $method->getReturnType();

if (!$returnType instanceof ReflectionNamedType) {
$this->failureReason = self::METHOD_DOES_NOT_HAVE_BOOL_RETURN_TYPE;

return false;
throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(
get_class($other),
$this->method
);
}

if ($returnType->allowsNull()) {
$this->failureReason = self::METHOD_DOES_NOT_HAVE_BOOL_RETURN_TYPE;

return false;
throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(
get_class($other),
$this->method
);
}

if ($returnType->getName() !== 'bool') {
$this->failureReason = self::METHOD_DOES_NOT_HAVE_BOOL_RETURN_TYPE;

return false;
throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(
get_class($other),
$this->method
);
}

if ($method->getNumberOfParameters() !== 1 || $method->getNumberOfRequiredParameters() !== 1) {
$this->failureReason = self::METHOD_DOES_NOT_ACCEPT_EXACTLY_ONE_ARGUMENT;

return false;
throw new ComparisonMethodDoesNotDeclareExactlyOneParameterException(
get_class($other),
$this->method
);
}

$parameter = $method->getParameters()[0];

if (!$parameter->hasType()) {
$this->failureReason = self::PARAMETER_DOES_NOT_HAVE_DECLARED_TYPE;

return false;
throw new ComparisonMethodDoesNotDeclareParameterTypeException(
get_class($other),
$this->method
);
}

$type = $parameter->getType();

if (!$type instanceof ReflectionNamedType) {
$this->failureReason = self::PARAMETER_DOES_NOT_HAVE_DECLARED_TYPE;

return false;
throw new ComparisonMethodDoesNotDeclareParameterTypeException(
get_class($other),
$this->method
);
}

$typeName = $type->getName();
Expand All @@ -133,73 +134,18 @@ protected function matches($other): bool
}

if (!$this->expected instanceof $typeName) {
$this->failureReason = self::EXPECTED_NOT_COMPATIBLE_WITH_PARAMETER_TYPE;

return false;
throw new ComparisonMethodDoesNotAcceptParameterTypeException(
get_class($other),
$this->method,
get_class($this->expected)
);
}

if ($other->{$this->method}($this->expected)) {
return true;
}

$this->failureReason = self::OBJECTS_ARE_NOT_EQUAL_ACCORDING_TO_METHOD;

return false;
return $other->{$this->method}($this->expected);
}

protected function failureDescription($other): string
{
return $this->toString();
}

protected function additionalFailureDescription($other): string
{
switch ($this->failureReason) {
case self::ACTUAL_IS_NOT_AN_OBJECT:
return 'Actual value is not an object.';

case self::ACTUAL_DOES_NOT_HAVE_METHOD:
return sprintf(
'%s::%s() does not exist.',
get_class($other),
$this->method
);

case self::METHOD_DOES_NOT_HAVE_BOOL_RETURN_TYPE:
return sprintf(
'%s::%s() does not declare a bool return type.',
get_class($other),
$this->method
);

case self::METHOD_DOES_NOT_ACCEPT_EXACTLY_ONE_ARGUMENT:
return sprintf(
'%s::%s() does not accept exactly one argument.',
get_class($other),
$this->method
);

case self::PARAMETER_DOES_NOT_HAVE_DECLARED_TYPE:
return sprintf(
'Parameter of %s::%s() does not have a declared type.',
get_class($other),
$this->method
);

case self::EXPECTED_NOT_COMPATIBLE_WITH_PARAMETER_TYPE:
return sprintf(
'%s is not accepted an accepted argument type for %s::%s().',
get_class($this->expected),
get_class($other),
$this->method
);

default:
return sprintf(
'The objects are not equal according to %s::%s().',
get_class($other),
$this->method
);
}
}
}
32 changes: 32 additions & 0 deletions src/Framework/Exception/ActualValueIsNotAnObjectException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;

use const PHP_EOL;

/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class ActualValueIsNotAnObjectException extends Exception
{
public function __construct()
{
parent::__construct(
'Actual value is not an object',
0,
null
);
}

public function __toString(): string
{
return $this->getMessage() . PHP_EOL;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;

use const PHP_EOL;
use function sprintf;

/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class ComparisonMethodDoesNotAcceptParameterTypeException extends Exception
{
public function __construct(string $className, string $methodName, string $type)
{
parent::__construct(
sprintf(
'%s is not an accepted argument type for comparison method %s::%s().',
$type,
$className,
$methodName
),
0,
null
);
}

public function __toString(): string
{
return $this->getMessage() . PHP_EOL;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;

use const PHP_EOL;
use function sprintf;

/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class ComparisonMethodDoesNotDeclareBoolReturnTypeException extends Exception
{
public function __construct(string $className, string $methodName)
{
parent::__construct(
sprintf(
'Comparison method %s::%s() does not declare bool return type.',
$className,
$methodName
),
0,
null
);
}

public function __toString(): string
{
return $this->getMessage() . PHP_EOL;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework;

use const PHP_EOL;
use function sprintf;

/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class ComparisonMethodDoesNotDeclareExactlyOneParameterException extends Exception
{
public function __construct(string $className, string $methodName)
{
parent::__construct(
sprintf(
'Comparison method %s::%s() does not declare exactly one parameter.',
$className,
$methodName
),
0,
null
);
}

public function __toString(): string
{
return $this->getMessage() . PHP_EOL;
}
}
Loading

0 comments on commit 6099c5e

Please sign in to comment.