Skip to content

Commit

Permalink
Overhaul thrown exceptions (#191)
Browse files Browse the repository at this point in the history
# Summary <!-- Required -->

~~Adds a `WP_MockException` object and overhauls exceptions used or
referenced within project.~~

After consideration, I decided to go for more specific exceptions in the
PHP stdlib - such as `RunTimeException` or `InvalidArgumentException`
when applicable. I realized that WP_Mock already uses other
domain-specific exceptions when it comes to assertions failed or mockery
exceptions, so perhaps at the moment we don't need a specific WP_Mock
exception.

Had to make additional changes due to PhpStan errors after updating
code.

### Closes: #189 

## Contributor checklist <!-- Required -->

<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you are unsure about any of these, please ask for
clarification. We are here to help! -->

- [x] I agree to follow this project's [**Code of
Conduct**](https://github.com/10up/.github/blob/trunk/CODE_OF_CONDUCT.md).
- [x] I have updated the documentation accordingly 
- [x] I have added tests to cover changes introduced by this pull
request
- [x] All new and existing tests pass

## Testing <!-- Required -->

<!-- If applicable, add specific steps for the reviewer to perform as
part of their testing process prior to approving this pull request. -->

- [ ] Tests and code analysis passes

<!-- List any configuration requirements for testing. -->

### Reviewer checklist <!-- Required -->

<!-- The following checklist is for the reviewer: add any steps that may
be relevant while reviewing this pull request -->

- [ ] Code changes review
- [ ] Documentation changes review
- [ ] Unit tests pass
- [ ] Static analysis passes
  • Loading branch information
agibson-godaddy authored Dec 29, 2022
2 parents cdac012 + fe8e5c3 commit 5733848
Show file tree
Hide file tree
Showing 10 changed files with 313 additions and 451 deletions.
11 changes: 5 additions & 6 deletions php/WP_Mock/API/function-mocks.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use PHPUnit\Framework\ExpectationFailedException;

if (! function_exists('add_action')) {
/**
* Hooks a function on to a specific action.
Expand Down Expand Up @@ -177,9 +179,8 @@ function esc_attr_x()
/**
* Dummy method for _n().
*
* @throws \PHPUnit\Framework\ExpectationFailedException Throws error if too few arguments.
*
* @return mixed singular or plural string based on number.
* @return mixed singular or plural string based on number
* @throws ExpectationFailedException if too few arguments passed
*/
function _n()
{
Expand All @@ -192,9 +193,7 @@ function _n()
return $args[1];
}
} else {
throw new \PHPUnit\Framework\ExpectationFailedException(
sprintf('Too few arguments to function %s', __FUNCTION__)
);
throw new ExpectationFailedException(sprintf('Too few arguments to function %s', __FUNCTION__));
}
}
}
91 changes: 44 additions & 47 deletions php/WP_Mock/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace WP_Mock;

use InvalidArgumentException;
use Mockery;
use Mockery\Matcher\AnyOf;
use Mockery\Matcher\Type;

class Functions
{
Expand Down Expand Up @@ -64,54 +67,47 @@ public function flush()
* Registers the function to be mocked and sets up its expectations
*
* @param string $function
* @param array $arguments
*
* @throws \Exception If the function name is invalid
*
* @param array<mixed> $arguments
* @return Mockery\Expectation
*/
public function register($function, $arguments)
public function register(string $function, array $arguments = [])
{
$expectation = null;
try {
$this->generate_function($function);
if (empty($this->mocked_functions[$function])) {
$this->mocked_functions[$function] = Mockery::mock('wp_api');
}
$mock = $this->mocked_functions[$function];
$this->generate_function($function);

$method = preg_replace('/\\\\+/', '_', $function);
$expectation = $this->set_up_mock($mock, $method, $arguments);
Handler::register_handler($function, array( $mock, $method ));
} catch (\Exception $e) {
throw $e;
if (empty($this->mocked_functions[$function])) {
$this->mocked_functions[$function] = Mockery::mock('wp_api');
}

$mock = $this->mocked_functions[$function];

$method = preg_replace('/\\\\+/', '_', $function);
$expectation = $this->set_up_mock($mock, $method, $arguments);

Handler::register_handler($function, [$mock, $method]);

return $expectation;
}

/**
* Sets up an argument placeholder that allows it to be any of an enumerated
* list of possibilities
* Sets up an argument placeholder that allows it to be any of an enumerated list of possibilities.
*
* @return \Mockery\Matcher\anyOf
* @return AnyOf
*/
public static function anyOf()
public static function anyOf(): AnyOf
{
return call_user_func_array(array( '\\Mockery', 'anyOf' ), func_get_args());
/** @phpstan-ignore-next-line */
return call_user_func_array(['\\Mockery', 'anyOf'], func_get_args());
}

/**
* Sets up an argument placeholder that requires the argument to be of a
* certain type
* Sets up an argument placeholder that requires the argument to be of a certain type.
*
* This may be any type for which there is a "is_*" function, or any class or
* interface.
* This may be any type for which there is a "is_*" function, or any class or interface.
*
* @param string $expected
*
* @return Mockery\Matcher\Type
* @return Type
*/
public static function type($expected)
public static function type(string $expected): Type
{
return Mockery::type($expected);
}
Expand Down Expand Up @@ -268,37 +264,38 @@ private function sanitize_function_name($function_name)
}

/**
* Validate the function name for format and other considerations
*
* Validation will fail if the string doesn't match the regex, if it's an
* internal function, or if it is a reserved word in PHP.
* Validate the function name for format and other considerations.
*
* @param string $function_name
* Validation will fail if not a valid function name, if it's an internal function, or if it is a reserved word in PHP.
*
* @throws \InvalidArgumentException
* @param string $functionName
* @return void
* @throws InvalidArgumentException
*/
private function validate_function_name($function_name)
private function validate_function_name(string $functionName): void
{
if (function_exists($function_name)) {
if (function_exists($functionName)) {
if (empty($this->internal_functions)) {
$defined_functions = get_defined_functions();
$this->internal_functions = $defined_functions['internal'];
$definedFunctions = get_defined_functions();
$this->internal_functions = $definedFunctions['internal'];
}
if (in_array($function_name, $this->internal_functions)) {
throw new \InvalidArgumentException('Cannot override internal PHP functions!');

if (in_array($functionName, $this->internal_functions)) {
throw new InvalidArgumentException('Cannot override internal PHP functions!');
}
}

$parts = explode('\\', $function_name);
$parts = explode('\\', $functionName);
$name = array_pop($parts);

if (! preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $function_name)) {
throw new \InvalidArgumentException('Function name not properly formatted!');
if (! preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $functionName)) {
throw new InvalidArgumentException('Function name not properly formatted!');
}

$reserved_words = ' __halt_compiler abstract and array as break callable case catch class clone const continue declare default die do echo else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval exit extends final for foreach function global goto if implements include include_once instanceof insteadof interface isset list namespace new or print private protected public require require_once return static switch throw trait try unset use var while xor __CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ ';
if (false !== strpos($reserved_words, " $name ")) {
throw new \InvalidArgumentException('Function name can not be a reserved word!');
$reservedWords = ' __halt_compiler abstract and array as break callable case catch class clone const continue declare default die do echo else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval exit extends final for foreach function global goto if implements include include_once instanceof insteadof interface isset list namespace new or print private protected public require require_once return static switch throw trait try unset use var while xor __CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ ';

if (false !== strpos($reservedWords, " $name ")) {
throw new InvalidArgumentException('Function name can not be a reserved word!');
}
}
}
69 changes: 39 additions & 30 deletions php/WP_Mock/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

namespace WP_Mock;

use Exception;
use PHPUnit\Framework\ExpectationFailedException;
use WP_Mock;

class Handler
{
/**
Expand All @@ -32,22 +36,22 @@ public static function register_handler($function_name, $callback)
/**
* Handle a mocked function call.
*
* @param string $function_name
* @param array $args
*
* @param string $functionName
* @param array<mixed> $args
* @return mixed
* @throws ExpectationFailedException
*/
public static function handle_function($function_name, $args = array())
public static function handle_function(string $functionName, array $args = [])
{
if (self::handler_exists($function_name)) {
$callback = self::$handlers[ $function_name ];
if (self::handler_exists($functionName)) {
$callback = self::$handlers[$functionName];

return call_user_func_array($callback, $args);
} elseif (\WP_Mock::strictMode()) {
throw new \PHPUnit\Framework\ExpectationFailedException(
sprintf('No handler found for %s', $function_name)
);
} elseif (WP_Mock::strictMode()) {
throw new ExpectationFailedException(sprintf('No handler found for %s', $functionName));
}

return null;
}

/**
Expand All @@ -71,43 +75,48 @@ public static function cleanup()
}

/**
* Helper function for common passthru return functions
* Helper function for common passthru return functions.
*
* @param string $function_name
* @param array $args
*
* @return mixed
* @param string $functionName
* @param array<mixed> $args
* @return ?mixed
* @throws ExpectationFailedException
*/
public static function predefined_return_function_helper($function_name, array $args)
public static function predefined_return_function_helper(string $functionName, array $args = [])
{
$result = self::handle_function($function_name, $args);
if (! self::handler_exists($function_name)) {
$result = isset($args[0]) ? $args[0] : $result;
$result = self::handle_function($functionName, $args);

if (! self::handler_exists($functionName)) {
$result = $args[0] ?? $result;
}

return $result;
}

/**
* Helper function for common echo functions
*
* @param string $function_name
* @param array $args
* Helper function for common echo functions.
*
* @throws \Exception
* @param string $functionName
* @param array<int, string> $args
* @return void
* @throws Exception|ExpectationFailedException
*/
public static function predefined_echo_function_helper($function_name, array $args)
public static function predefined_echo_function_helper(string $functionName, array $args = []): void
{
ob_start();

try {
self::handle_function($function_name, $args);
} catch (\Exception $exception) {
self::handle_function($functionName, $args);
} catch (Exception $exception) {
ob_end_clean();
/** @phpstan-ignore-next-line */
throw $exception;
}
$result = ob_get_clean();
if (! self::handler_exists($function_name)) {
$result = isset($args[0]) ? $args[0] : $result;

$result = ob_get_clean() ?: '';

if (! self::handler_exists($functionName)) {
$result = $args[0] ?? $result;
}

echo $result;
Expand Down
13 changes: 8 additions & 5 deletions php/WP_Mock/Hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace WP_Mock;

use PHPUnit\Framework\ExpectationFailedException;
use WP_Mock;
use WP_Mock\Matcher\AnyInstance;

abstract class Hook
Expand Down Expand Up @@ -77,14 +79,15 @@ public function with()
abstract protected function new_responder();

/**
* Throw an exception if strict mode is on
* Throws an exception if strict mode is on.
*
* @throws \PHPUnit\Framework\ExpectationFailedException
* @return void
* @throws ExpectationFailedException
*/
protected function strict_check()
protected function strict_check(): void
{
if (\WP_Mock::strictMode()) {
throw new \PHPUnit\Framework\ExpectationFailedException($this->get_strict_mode_message());
if (WP_Mock::strictMode()) {
throw new ExpectationFailedException($this->get_strict_mode_message());
}
}

Expand Down
Loading

0 comments on commit 5733848

Please sign in to comment.