allow vendord
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s

This commit is contained in:
2026-03-30 14:54:57 +07:00
parent 66aed7c4e8
commit b5e3a778ce
21316 changed files with 2777892 additions and 13 deletions

1062
vendor/mockery/mockery/library/Mockery.php vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,86 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Adapter\Phpunit;
use Mockery;
use PHPUnit\Framework\Attributes\After;
use PHPUnit\Framework\Attributes\Before;
use function method_exists;
/**
* Integrates Mockery into PHPUnit. Ensures Mockery expectations are verified
* for each test and are included by the assertion counter.
*/
trait MockeryPHPUnitIntegration
{
use MockeryPHPUnitIntegrationAssertPostConditions;
protected $mockeryOpen;
protected function addMockeryExpectationsToAssertionCount()
{
$this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount());
}
protected function checkMockeryExceptions()
{
if (! method_exists($this, 'markAsRisky')) {
return;
}
foreach (Mockery::getContainer()->mockery_thrownExceptions() as $e) {
if (! $e->dismissed()) {
$this->markAsRisky();
}
}
}
protected function closeMockery()
{
Mockery::close();
$this->mockeryOpen = false;
}
/**
* Performs assertions shared by all tests of a test case. This method is
* called before execution of a test ends and before the tearDown method.
*/
protected function mockeryAssertPostConditions()
{
$this->addMockeryExpectationsToAssertionCount();
$this->checkMockeryExceptions();
$this->closeMockery();
parent::assertPostConditions();
}
/**
* @after
*/
#[After]
protected function purgeMockeryContainer()
{
if ($this->mockeryOpen) {
// post conditions wasn't called, so test probably failed
Mockery::close();
}
}
/**
* @before
*/
#[Before]
protected function startMockery()
{
$this->mockeryOpen = true;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
declare(strict_types=1);
namespace Mockery\Adapter\Phpunit;
trait MockeryPHPUnitIntegrationAssertPostConditions
{
protected function assertPostConditions(): void
{
$this->mockeryAssertPostConditions();
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Adapter\Phpunit;
use PHPUnit\Framework\TestCase;
abstract class MockeryTestCase extends TestCase
{
use MockeryPHPUnitIntegration;
use MockeryTestCaseSetUp;
protected function mockeryTestSetUp()
{
}
protected function mockeryTestTearDown()
{
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
declare(strict_types=1);
namespace Mockery\Adapter\Phpunit;
trait MockeryTestCaseSetUp
{
protected function setUp(): void
{
parent::setUp();
$this->mockeryTestSetUp();
}
protected function tearDown(): void
{
$this->mockeryTestTearDown();
parent::tearDown();
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Adapter\Phpunit;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListener as PHPUnitTestListener;
use PHPUnit\Framework\TestListenerDefaultImplementation;
use PHPUnit\Framework\TestSuite;
class TestListener implements PHPUnitTestListener
{
use TestListenerDefaultImplementation;
private $trait;
public function __construct()
{
$this->trait = new TestListenerTrait();
}
public function endTest(Test $test, float $time): void
{
$this->trait->endTest($test, $time);
}
public function startTestSuite(TestSuite $suite): void
{
$this->trait->startTestSuite();
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Adapter\Phpunit;
use LogicException;
use Mockery;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Runner\BaseTestRunner;
use PHPUnit\Util\Blacklist;
use ReflectionClass;
use function dirname;
use function method_exists;
use function sprintf;
class TestListenerTrait
{
/**
* endTest is called after each test and checks if \Mockery::close() has
* been called, and will let the test fail if it hasn't.
*
* @param Test $test
* @param float $time
*/
public function endTest(Test $test, $time)
{
if (! $test instanceof TestCase) {
// We need the getTestResultObject and getStatus methods which are
// not part of the interface.
return;
}
if ($test->getStatus() !== BaseTestRunner::STATUS_PASSED) {
// If the test didn't pass there is no guarantee that
// verifyMockObjects and assertPostConditions have been called.
// And even if it did, the point here is to prevent false
// negatives, not to make failing tests fail for more reasons.
return;
}
try {
// The self() call is used as a sentinel. Anything that throws if
// the container is closed already will do.
Mockery::self();
} catch (LogicException $logicException) {
return;
}
$e = new ExpectationFailedException(
sprintf(
"Mockery's expectations have not been verified. Make sure that \Mockery::close() is called at the end of the test. Consider using %s\MockeryPHPUnitIntegration or extending %s\MockeryTestCase.",
__NAMESPACE__,
__NAMESPACE__
)
);
/** @var \PHPUnit\Framework\TestResult $result */
$result = $test->getTestResultObject();
if ($result !== null) {
$result->addFailure($test, $e, $time);
}
}
public function startTestSuite()
{
if (method_exists(Blacklist::class, 'addDirectory')) {
(new Blacklist())->getBlacklistedDirectories();
Blacklist::addDirectory(dirname((new ReflectionClass(Mockery::class))->getFileName()));
} else {
Blacklist::$blacklistedClassNames[Mockery::class] = 1;
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use Closure;
use function func_get_args;
/**
* @internal
*/
class ClosureWrapper
{
private $closure;
public function __construct(Closure $closure)
{
$this->closure = $closure;
}
/**
* @return mixed
*/
public function __invoke()
{
return ($this->closure)(...func_get_args());
}
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use function array_map;
use function current;
use function implode;
use function reset;
class CompositeExpectation implements ExpectationInterface
{
/**
* Stores an array of all expectations for this composite
*
* @var array<ExpectationInterface>
*/
protected $_expectations = [];
/**
* Intercept any expectation calls and direct against all expectations
*
* @param string $method
*
* @return self
*/
public function __call($method, array $args)
{
foreach ($this->_expectations as $expectation) {
$expectation->{$method}(...$args);
}
return $this;
}
/**
* Return the string summary of this composite expectation
*
* @return string
*/
public function __toString()
{
$parts = array_map(static function (ExpectationInterface $expectation): string {
return (string) $expectation;
}, $this->_expectations);
return '[' . implode(', ', $parts) . ']';
}
/**
* Add an expectation to the composite
*
* @param ExpectationInterface|HigherOrderMessage $expectation
*
* @return void
*/
public function add($expectation)
{
$this->_expectations[] = $expectation;
}
/**
* @param mixed ...$args
*/
public function andReturn(...$args)
{
return $this->__call(__FUNCTION__, $args);
}
/**
* Set a return value, or sequential queue of return values
*
* @param mixed ...$args
*
* @return self
*/
public function andReturns(...$args)
{
return $this->andReturn(...$args);
}
/**
* Return the parent mock of the first expectation
*
* @return LegacyMockInterface&MockInterface
*/
public function getMock()
{
reset($this->_expectations);
$first = current($this->_expectations);
return $first->getMock();
}
/**
* Return order number of the first expectation
*
* @return int
*/
public function getOrderNumber()
{
reset($this->_expectations);
$first = current($this->_expectations);
return $first->getOrderNumber();
}
/**
* Mockery API alias to getMock
*
* @return LegacyMockInterface&MockInterface
*/
public function mock()
{
return $this->getMock();
}
/**
* Starts a new expectation addition on the first mock which is the primary target outside of a demeter chain
*
* @param mixed ...$args
*
* @return Expectation
*/
public function shouldNotReceive(...$args)
{
reset($this->_expectations);
$first = current($this->_expectations);
return $first->getMock()->shouldNotReceive(...$args);
}
/**
* Starts a new expectation addition on the first mock which is the primary target, outside of a demeter chain
*
* @param mixed ...$args
*
* @return Expectation
*/
public function shouldReceive(...$args)
{
reset($this->_expectations);
$first = current($this->_expectations);
return $first->getMock()->shouldReceive(...$args);
}
}

View File

@@ -0,0 +1,406 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use Closure;
use Hamcrest\Matcher;
use Hamcrest_Matcher;
use InvalidArgumentException;
use LogicException;
use Mockery\Matcher\MatcherInterface;
use function array_key_exists;
use function array_merge;
use function class_implements;
use function get_parent_class;
use function is_a;
use function sprintf;
use function strtolower;
use function trigger_error;
use const E_USER_DEPRECATED;
use const PHP_MAJOR_VERSION;
class Configuration
{
/**
* Boolean assertion of whether we ignore unnecessary mocking of methods,
* i.e. when method expectations are made, set using a zeroOrMoreTimes()
* constraint, and then never called. Essentially such expectations are
* not required and are just taking up test space.
*
* @var bool
*/
protected $_allowMockingMethodsUnnecessarily = true;
/**
* Boolean assertion of whether we can mock methods which do not actually
* exist for the given class or object (ignored for unreal mocks)
*
* @var bool
*/
protected $_allowMockingNonExistentMethod = true;
/**
* Constants map
*
* e.g. ['class' => ['MY_CONST' => 123, 'OTHER_CONST' => 'foo']]
*
* @var array<class-string,array<string,array<scalar>|scalar>>
*/
protected $_constantsMap = [];
/**
* Default argument matchers
*
* e.g. ['class' => 'matcher']
*
* @var array<class-string,class-string>
*/
protected $_defaultMatchers = [];
/**
* Parameter map for use with PHP internal classes.
*
* e.g. ['class' => ['method' => ['param1', 'param2']]]
*
* @var array<class-string,array<string,list<string>>>
*/
protected $_internalClassParamMap = [];
/**
* Custom object formatters
*
* e.g. ['class' => static fn($object) => 'formatted']
*
* @var array<class-string,Closure>
*/
protected $_objectFormatters = [];
/**
* @var QuickDefinitionsConfiguration
*/
protected $_quickDefinitionsConfiguration;
/**
* Boolean assertion is reflection caching enabled or not. It should be
* always enabled, except when using PHPUnit's --static-backup option.
*
* @see https://github.com/mockery/mockery/issues/268
*/
protected $_reflectionCacheEnabled = true;
public function __construct()
{
$this->_quickDefinitionsConfiguration = new QuickDefinitionsConfiguration();
}
/**
* Set boolean to allow/prevent unnecessary mocking of methods
*
* @param bool $flag
*
* @return void
*
* @deprecated since 1.4.0
*/
public function allowMockingMethodsUnnecessarily($flag = true)
{
@trigger_error(
sprintf('The %s method is deprecated and will be removed in a future version of Mockery', __METHOD__),
E_USER_DEPRECATED
);
$this->_allowMockingMethodsUnnecessarily = (bool) $flag;
}
/**
* Set boolean to allow/prevent mocking of non-existent methods
*
* @param bool $flag
*
* @return void
*/
public function allowMockingNonExistentMethods($flag = true)
{
$this->_allowMockingNonExistentMethod = (bool) $flag;
}
/**
* Disable reflection caching
*
* It should be always enabled, except when using
* PHPUnit's --static-backup option.
*
* @see https://github.com/mockery/mockery/issues/268
*
* @return void
*/
public function disableReflectionCache()
{
$this->_reflectionCacheEnabled = false;
}
/**
* Enable reflection caching
*
* It should be always enabled, except when using
* PHPUnit's --static-backup option.
*
* @see https://github.com/mockery/mockery/issues/268
*
* @return void
*/
public function enableReflectionCache()
{
$this->_reflectionCacheEnabled = true;
}
/**
* Get the map of constants to be used in the mock generator
*
* @return array<class-string,array<string,array<scalar>|scalar>>
*/
public function getConstantsMap()
{
return $this->_constantsMap;
}
/**
* Get the default matcher for a given class
*
* @param class-string $class
*
* @return null|class-string
*/
public function getDefaultMatcher($class)
{
$classes = [];
$parentClass = $class;
do {
$classes[] = $parentClass;
$parentClass = get_parent_class($parentClass);
} while ($parentClass !== false);
$classesAndInterfaces = array_merge($classes, class_implements($class));
foreach ($classesAndInterfaces as $type) {
if (array_key_exists($type, $this->_defaultMatchers)) {
return $this->_defaultMatchers[$type];
}
}
return null;
}
/**
* Get the parameter map of an internal PHP class method
*
* @param class-string $class
* @param string $method
*
* @return null|array
*/
public function getInternalClassMethodParamMap($class, $method)
{
$class = strtolower($class);
$method = strtolower($method);
if (! array_key_exists($class, $this->_internalClassParamMap)) {
return null;
}
if (! array_key_exists($method, $this->_internalClassParamMap[$class])) {
return null;
}
return $this->_internalClassParamMap[$class][$method];
}
/**
* Get the parameter maps of internal PHP classes
*
* @return array<class-string,array<string,list<string>>>
*/
public function getInternalClassMethodParamMaps()
{
return $this->_internalClassParamMap;
}
/**
* Get the object formatter for a class
*
* @param class-string $class
* @param Closure $defaultFormatter
*
* @return Closure
*/
public function getObjectFormatter($class, $defaultFormatter)
{
$parentClass = $class;
do {
$classes[] = $parentClass;
$parentClass = get_parent_class($parentClass);
} while ($parentClass !== false);
$classesAndInterfaces = array_merge($classes, class_implements($class));
foreach ($classesAndInterfaces as $type) {
if (array_key_exists($type, $this->_objectFormatters)) {
return $this->_objectFormatters[$type];
}
}
return $defaultFormatter;
}
/**
* Returns the quick definitions configuration
*/
public function getQuickDefinitions(): QuickDefinitionsConfiguration
{
return $this->_quickDefinitionsConfiguration;
}
/**
* Return flag indicating whether mocking non-existent methods allowed
*
* @return bool
*
* @deprecated since 1.4.0
*/
public function mockingMethodsUnnecessarilyAllowed()
{
@trigger_error(
sprintf('The %s method is deprecated and will be removed in a future version of Mockery', __METHOD__),
E_USER_DEPRECATED
);
return $this->_allowMockingMethodsUnnecessarily;
}
/**
* Return flag indicating whether mocking non-existent methods allowed
*
* @return bool
*/
public function mockingNonExistentMethodsAllowed()
{
return $this->_allowMockingNonExistentMethod;
}
/**
* Is reflection cache enabled?
*
* @return bool
*/
public function reflectionCacheEnabled()
{
return $this->_reflectionCacheEnabled;
}
/**
* Remove all overridden parameter maps from internal PHP classes.
*
* @return void
*/
public function resetInternalClassMethodParamMaps()
{
$this->_internalClassParamMap = [];
}
/**
* Set a map of constants to be used in the mock generator
*
* e.g. ['MyClass' => ['MY_CONST' => 123, 'ARRAY_CONST' => ['foo', 'bar']]]
*
* @param array<class-string,array<string,array<scalar>|scalar>> $map
*
* @return void
*/
public function setConstantsMap(array $map)
{
$this->_constantsMap = $map;
}
/**
* @param class-string $class
* @param class-string $matcherClass
*
* @throws InvalidArgumentException
*
* @return void
*/
public function setDefaultMatcher($class, $matcherClass)
{
$isHamcrest = is_a($matcherClass, Matcher::class, true)
|| is_a($matcherClass, Hamcrest_Matcher::class, true);
if ($isHamcrest) {
@trigger_error('Hamcrest package has been deprecated and will be removed in 2.0', E_USER_DEPRECATED);
}
if (! $isHamcrest && ! is_a($matcherClass, MatcherInterface::class, true)) {
throw new InvalidArgumentException(sprintf(
"Matcher class must implement %s, '%s' given.",
MatcherInterface::class,
$matcherClass
));
}
$this->_defaultMatchers[$class] = $matcherClass;
}
/**
* Set a parameter map (array of param signature strings) for the method of an internal PHP class.
*
* @param class-string $class
* @param string $method
* @param list<string> $map
*
* @throws LogicException
*
* @return void
*/
public function setInternalClassMethodParamMap($class, $method, array $map)
{
if (PHP_MAJOR_VERSION > 7) {
throw new LogicException(
'Internal class parameter overriding is not available in PHP 8. Incompatible signatures have been reclassified as fatal errors.'
);
}
$class = strtolower($class);
if (! array_key_exists($class, $this->_internalClassParamMap)) {
$this->_internalClassParamMap[$class] = [];
}
$this->_internalClassParamMap[$class][strtolower($method)] = $map;
}
/**
* Set a custom object formatter for a class
*
* @param class-string $class
* @param Closure $formatterCallback
*
* @return void
*/
public function setObjectFormatter($class, $formatterCallback)
{
$this->_objectFormatters[$class] = $formatterCallback;
}
}

View File

@@ -0,0 +1,678 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use Closure;
use Exception as PHPException;
use Mockery;
use Mockery\Exception\InvalidOrderException;
use Mockery\Exception\RuntimeException;
use Mockery\Generator\Generator;
use Mockery\Generator\MockConfigurationBuilder;
use Mockery\Loader\Loader as LoaderInterface;
use ReflectionClass;
use ReflectionException;
use Throwable;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_pop;
use function array_shift;
use function array_values;
use function class_exists;
use function count;
use function explode;
use function get_class;
use function interface_exists;
use function is_callable;
use function is_object;
use function is_string;
use function md5;
use function preg_grep;
use function preg_match;
use function range;
use function reset;
use function rtrim;
use function sprintf;
use function str_replace;
use function strlen;
use function strpos;
use function strtolower;
use function substr;
use function trait_exists;
/**
* Container for mock objects
*
* @template TMockObject of object
*/
class Container
{
public const BLOCKS = Mockery::BLOCKS;
/**
* Order number of allocation
*
* @var int
*/
protected $_allocatedOrder = 0;
/**
* Current ordered number
*
* @var int
*/
protected $_currentOrder = 0;
/**
* @var Generator
*/
protected $_generator;
/**
* Ordered groups
*
* @var array<string,int>
*/
protected $_groups = [];
/**
* @var LoaderInterface
*/
protected $_loader;
/**
* Store of mock objects
*
* @var array<class-string<LegacyMockInterface&MockInterface&TMockObject>|array-key,LegacyMockInterface&MockInterface&TMockObject>
*/
protected $_mocks = [];
/**
* @var array<string,string>
*/
protected $_namedMocks = [];
/**
* @var Instantiator
*/
protected $instantiator;
public function __construct(?Generator $generator = null, ?LoaderInterface $loader = null, ?Instantiator $instantiator = null)
{
$this->_generator = $generator instanceof Generator ? $generator : Mockery::getDefaultGenerator();
$this->_loader = $loader instanceof LoaderInterface ? $loader : Mockery::getDefaultLoader();
$this->instantiator = $instantiator instanceof Instantiator ? $instantiator : new Instantiator();
}
/**
* Return a specific remembered mock according to the array index it
* was stored to in this container instance
*
* @template TMock of object
*
* @param class-string<TMock> $reference
*
* @return null|(LegacyMockInterface&MockInterface&TMock)
*/
public function fetchMock($reference)
{
return $this->_mocks[$reference] ?? null;
}
/**
* @return Generator
*/
public function getGenerator()
{
return $this->_generator;
}
/**
* @param string $method
* @param string $parent
*
* @return null|string
*/
public function getKeyOfDemeterMockFor($method, $parent)
{
$keys = array_keys($this->_mocks);
$match = preg_grep('/__demeter_' . md5($parent) . sprintf('_%s$/', $method), $keys);
if ($match === false) {
return null;
}
if ($match === []) {
return null;
}
return array_values($match)[0];
}
/**
* @return LoaderInterface
*/
public function getLoader()
{
return $this->_loader;
}
/**
* @template TMock of object
* @return array<class-string<LegacyMockInterface&MockInterface&TMockObject>|array-key,LegacyMockInterface&MockInterface&TMockObject>
*/
public function getMocks()
{
return $this->_mocks;
}
/**
* @return void
*/
public function instanceMock()
{
}
/**
* see http://php.net/manual/en/language.oop5.basic.php
*
* @param string $className
*
* @return bool
*/
public function isValidClassName($className)
{
if ($className[0] === '\\') {
$className = substr($className, 1); // remove the first backslash
}
// all the namespaces and class name should match the regex
return array_filter(
explode('\\', $className),
static function ($name): bool {
return ! preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $name);
}
) === [];
}
/**
* Generates a new mock object for this container
*
* I apologies in advance for this. A God Method just fits the API which
* doesn't require differentiating between classes, interfaces, abstracts,
* names or partials - just so long as it's something that can be mocked.
* I'll refactor it one day so it's easier to follow.
*
* @template TMock of object
*
* @param array<class-string<TMock>|TMock|Closure(LegacyMockInterface&MockInterface&TMock):LegacyMockInterface&MockInterface&TMock|array<TMock>> $args
*
* @throws ReflectionException|RuntimeException
*
* @return LegacyMockInterface&MockInterface&TMock
*/
public function mock(...$args)
{
/** @var null|MockConfigurationBuilder $builder */
$builder = null;
/** @var null|callable $expectationClosure */
$expectationClosure = null;
$partialMethods = null;
$quickDefinitions = [];
$constructorArgs = null;
$blocks = [];
if (count($args) > 1) {
$finalArg = array_pop($args);
if (is_callable($finalArg) && is_object($finalArg)) {
$expectationClosure = $finalArg;
} else {
$args[] = $finalArg;
}
}
foreach ($args as $k => $arg) {
if ($arg instanceof MockConfigurationBuilder) {
$builder = $arg;
unset($args[$k]);
}
}
reset($args);
$builder = $builder ?? new MockConfigurationBuilder();
$mockeryConfiguration = Mockery::getConfiguration();
$builder->setParameterOverrides($mockeryConfiguration->getInternalClassMethodParamMaps());
$builder->setConstantsMap($mockeryConfiguration->getConstantsMap());
while ($args !== []) {
$arg = array_shift($args);
// check for multiple interfaces
if (is_string($arg)) {
foreach (explode('|', $arg) as $type) {
if ($arg === 'null') {
// skip PHP 8 'null's
continue;
}
if (strpos($type, ',') && !strpos($type, ']')) {
$interfaces = explode(',', str_replace(' ', '', $type));
$builder->addTargets($interfaces);
continue;
}
if (strpos($type, 'alias:') === 0) {
$type = str_replace('alias:', '', $type);
$builder->addTarget('stdClass');
$builder->setName($type);
continue;
}
if (strpos($type, 'overload:') === 0) {
$type = str_replace('overload:', '', $type);
$builder->setInstanceMock(true);
$builder->addTarget('stdClass');
$builder->setName($type);
continue;
}
if ($type[strlen($type) - 1] === ']') {
$parts = explode('[', $type);
$class = $parts[0];
if (! class_exists($class, true) && ! interface_exists($class, true)) {
throw new Exception('Can only create a partial mock from an existing class or interface');
}
$builder->addTarget($class);
$partialMethods = array_filter(
explode(',', strtolower(rtrim(str_replace(' ', '', $parts[1]), ']')))
);
foreach ($partialMethods as $partialMethod) {
if ($partialMethod[0] === '!') {
$builder->addBlackListedMethod(substr($partialMethod, 1));
continue;
}
$builder->addWhiteListedMethod($partialMethod);
}
continue;
}
if (class_exists($type, true) || interface_exists($type, true) || trait_exists($type, true)) {
$builder->addTarget($type);
continue;
}
if (! $mockeryConfiguration->mockingNonExistentMethodsAllowed()) {
throw new Exception(sprintf("Mockery can't find '%s' so can't mock it", $type));
}
if (! $this->isValidClassName($type)) {
throw new Exception('Class name contains invalid characters');
}
$builder->addTarget($type);
// unions are "sum" types and not "intersections", and so we must only process the first part
break;
}
continue;
}
if (is_object($arg)) {
$builder->addTarget($arg);
continue;
}
if (is_array($arg)) {
if ([] !== $arg && array_keys($arg) !== range(0, count($arg) - 1)) {
// if associative array
if (array_key_exists(self::BLOCKS, $arg)) {
$blocks = $arg[self::BLOCKS];
}
unset($arg[self::BLOCKS]);
$quickDefinitions = $arg;
continue;
}
$constructorArgs = $arg;
continue;
}
throw new Exception(sprintf(
'Unable to parse arguments sent to %s::mock()', get_class($this)
));
}
$builder->addBlackListedMethods($blocks);
if ($constructorArgs !== null) {
$builder->addBlackListedMethod('__construct'); // we need to pass through
} else {
$builder->setMockOriginalDestructor(true);
}
if ($partialMethods !== null && $constructorArgs === null) {
$constructorArgs = [];
}
$config = $builder->getMockConfiguration();
$this->checkForNamedMockClashes($config);
$def = $this->getGenerator()->generate($config);
$className = $def->getClassName();
if (class_exists($className, $attemptAutoload = false)) {
$rfc = new ReflectionClass($className);
if (! $rfc->implementsInterface(LegacyMockInterface::class)) {
throw new RuntimeException(sprintf('Could not load mock %s, class already exists', $className));
}
}
$this->getLoader()->load($def);
$mock = $this->_getInstance($className, $constructorArgs);
$mock->mockery_init($this, $config->getTargetObject(), $config->isInstanceMock());
if ($quickDefinitions !== []) {
if ($mockeryConfiguration->getQuickDefinitions()->shouldBeCalledAtLeastOnce()) {
$mock->shouldReceive($quickDefinitions)->atLeast()->once();
} else {
$mock->shouldReceive($quickDefinitions)->byDefault();
}
}
// if the last parameter passed to mock() is a closure,
if ($expectationClosure instanceof Closure) {
// call the closure with the mock object
$expectationClosure($mock);
}
return $this->rememberMock($mock);
}
/**
* Fetch the next available allocation order number
*
* @return int
*/
public function mockery_allocateOrder()
{
return ++$this->_allocatedOrder;
}
/**
* Reset the container to its original state
*
* @return void
*/
public function mockery_close()
{
foreach ($this->_mocks as $mock) {
$mock->mockery_teardown();
}
$this->_mocks = [];
}
/**
* Get current ordered number
*
* @return int
*/
public function mockery_getCurrentOrder()
{
return $this->_currentOrder;
}
/**
* Gets the count of expectations on the mocks
*
* @return int
*/
public function mockery_getExpectationCount()
{
$count = 0;
foreach ($this->_mocks as $mock) {
$count += $mock->mockery_getExpectationCount();
}
return $count;
}
/**
* Fetch array of ordered groups
*
* @return array<string,int>
*/
public function mockery_getGroups()
{
return $this->_groups;
}
/**
* Set current ordered number
*
* @param int $order
*
* @return int The current order number that was set
*/
public function mockery_setCurrentOrder($order)
{
return $this->_currentOrder = $order;
}
/**
* Set ordering for a group
*
* @param string $group
* @param int $order
*
* @return void
*/
public function mockery_setGroup($group, $order)
{
$this->_groups[$group] = $order;
}
/**
* Tear down tasks for this container
*
* @throws PHPException
*/
public function mockery_teardown()
{
try {
$this->mockery_verify();
} catch (PHPException $phpException) {
$this->mockery_close();
throw $phpException;
}
}
/**
* Retrieves all exceptions thrown by mocks
*
* @return array<Throwable>
*/
public function mockery_thrownExceptions()
{
/** @var array<Throwable> $exceptions */
$exceptions = [];
foreach ($this->_mocks as $mock) {
foreach ($mock->mockery_thrownExceptions() as $exception) {
$exceptions[] = $exception;
}
}
return $exceptions;
}
/**
* Validate the current mock's ordering
*
* @param string $method
* @param int $order
*
* @throws Exception
*/
public function mockery_validateOrder($method, $order, LegacyMockInterface $mock)
{
if ($order < $this->_currentOrder) {
$exception = new InvalidOrderException(
sprintf(
'Method %s called out of order: expected order %d, was %d',
$method,
$order,
$this->_currentOrder
)
);
$exception->setMock($mock)
->setMethodName($method)
->setExpectedOrder($order)
->setActualOrder($this->_currentOrder);
throw $exception;
}
$this->mockery_setCurrentOrder($order);
}
/**
* Verify the container mocks
*/
public function mockery_verify()
{
foreach ($this->_mocks as $mock) {
$mock->mockery_verify();
}
}
/**
* Store a mock and set its container reference
*
* @template TRememberMock of object
*
* @param LegacyMockInterface&MockInterface&TRememberMock $mock
*
* @return LegacyMockInterface&MockInterface&TRememberMock
*/
public function rememberMock(LegacyMockInterface $mock)
{
$class = get_class($mock);
if (! array_key_exists($class, $this->_mocks)) {
return $this->_mocks[$class] = $mock;
}
/**
* This condition triggers for an instance mock where origin mock
* is already remembered
*/
return $this->_mocks[] = $mock;
}
/**
* Retrieve the last remembered mock object,
* which is the same as saying retrieve the current mock being programmed where you have yet to call mock()
* to change it thus why the method name is "self" since it will be used during the programming of the same mock.
*
* @return LegacyMockInterface|MockInterface
*/
public function self()
{
$mocks = array_values($this->_mocks);
$index = count($mocks) - 1;
return $mocks[$index];
}
/**
* @template TMock of object
* @template TMixed
*
* @param class-string<TMock> $mockName
* @param null|array<TMixed> $constructorArgs
*
* @return TMock
*/
protected function _getInstance($mockName, $constructorArgs = null)
{
if ($constructorArgs !== null) {
return (new ReflectionClass($mockName))->newInstanceArgs($constructorArgs);
}
try {
$instance = $this->instantiator->instantiate($mockName);
} catch (PHPException $phpException) {
/** @var class-string<TMock> $internalMockName */
$internalMockName = $mockName . '_Internal';
if (! class_exists($internalMockName)) {
eval(sprintf(
'class %s extends %s { public function __construct() {} }',
$internalMockName,
$mockName
));
}
$instance = new $internalMockName();
}
return $instance;
}
protected function checkForNamedMockClashes($config)
{
$name = $config->getName();
if ($name === null) {
return;
}
$hash = $config->getHash();
if (array_key_exists($name, $this->_namedMocks) && $hash !== $this->_namedMocks[$name]) {
throw new Exception(
sprintf("The mock named '%s' has been already defined with a different mock configuration", $name)
);
}
$this->_namedMocks[$name] = $hash;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\CountValidator;
use Mockery\Exception\InvalidCountException;
use const PHP_EOL;
class AtLeast extends CountValidatorAbstract
{
/**
* Checks if the validator can accept an additional nth call
*
* @param int $n
*
* @return bool
*/
public function isEligible($n)
{
return true;
}
/**
* Validate the call count against this validator
*
* @param int $n
*
* @throws InvalidCountException
* @return bool
*/
public function validate($n)
{
if ($this->_limit > $n) {
$exception = new InvalidCountException(
'Method ' . (string) $this->_expectation
. ' from ' . $this->_expectation->getMock()->mockery_getName()
. ' should be called' . PHP_EOL
. ' at least ' . $this->_limit . ' times but called ' . $n
. ' times.'
);
$exception->setMock($this->_expectation->getMock())
->setMethodName((string) $this->_expectation)
->setExpectedCountComparative('>=')
->setExpectedCount($this->_limit)
->setActualCount($n);
throw $exception;
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\CountValidator;
use Mockery\Exception\InvalidCountException;
use const PHP_EOL;
class AtMost extends CountValidatorAbstract
{
/**
* Validate the call count against this validator
*
* @param int $n
*
* @throws InvalidCountException
* @return bool
*/
public function validate($n)
{
if ($this->_limit < $n) {
$exception = new InvalidCountException(
'Method ' . (string) $this->_expectation
. ' from ' . $this->_expectation->getMock()->mockery_getName()
. ' should be called' . PHP_EOL
. ' at most ' . $this->_limit . ' times but called ' . $n
. ' times.'
);
$exception->setMock($this->_expectation->getMock())
->setMethodName((string) $this->_expectation)
->setExpectedCountComparative('<=')
->setExpectedCount($this->_limit)
->setActualCount($n);
throw $exception;
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\CountValidator;
use Mockery\Expectation;
abstract class CountValidatorAbstract implements CountValidatorInterface
{
/**
* Expectation for which this validator is assigned
*
* @var Expectation
*/
protected $_expectation = null;
/**
* Call count limit
*
* @var int
*/
protected $_limit = null;
/**
* Set Expectation object and upper call limit
*
* @param int $limit
*/
public function __construct(Expectation $expectation, $limit)
{
$this->_expectation = $expectation;
$this->_limit = $limit;
}
/**
* Checks if the validator can accept an additional nth call
*
* @param int $n
*
* @return bool
*/
public function isEligible($n)
{
return $n < $this->_limit;
}
/**
* Validate the call count against this validator
*
* @param int $n
*
* @return bool
*/
abstract public function validate($n);
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Mockery\CountValidator;
interface CountValidatorInterface
{
/**
* Checks if the validator can accept an additional nth call
*
* @param int $n
*
* @return bool
*/
public function isEligible($n);
/**
* Validate the call count against this validator
*
* @param int $n
*
* @return bool
*/
public function validate($n);
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\CountValidator;
use Mockery\Exception\InvalidCountException;
use const PHP_EOL;
class Exact extends CountValidatorAbstract
{
/**
* Validate the call count against this validator
*
* @param int $n
*
* @throws InvalidCountException
* @return bool
*/
public function validate($n)
{
if ($this->_limit !== $n) {
$because = $this->_expectation->getExceptionMessage();
$exception = new InvalidCountException(
'Method ' . (string) $this->_expectation
. ' from ' . $this->_expectation->getMock()->mockery_getName()
. ' should be called' . PHP_EOL
. ' exactly ' . $this->_limit . ' times but called ' . $n
. ' times.'
. ($because ? ' Because ' . $this->_expectation->getExceptionMessage() : '')
);
$exception->setMock($this->_expectation->getMock())
->setMethodName((string) $this->_expectation)
->setExpectedCountComparative('=')
->setExpectedCount($this->_limit)
->setActualCount($n);
throw $exception;
}
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\CountValidator;
use Mockery\Exception\MockeryExceptionInterface;
use OutOfBoundsException;
class Exception extends OutOfBoundsException implements MockeryExceptionInterface
{
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use Mockery\Exception\MockeryExceptionInterface;
use UnexpectedValueException;
class Exception extends UnexpectedValueException implements MockeryExceptionInterface
{
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Exception;
class BadMethodCallException extends \BadMethodCallException implements MockeryExceptionInterface
{
/**
* @var bool
*/
private $dismissed = false;
public function dismiss()
{
$this->dismissed = true;
// we sometimes stack them
$previous = $this->getPrevious();
if (! $previous instanceof self) {
return;
}
$previous->dismiss();
}
/**
* @return bool
*/
public function dismissed()
{
return $this->dismissed;
}
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements MockeryExceptionInterface
{
}

View File

@@ -0,0 +1,152 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Exception;
use Mockery\CountValidator\Exception;
use Mockery\LegacyMockInterface;
use function in_array;
class InvalidCountException extends Exception
{
/**
* @var int|null
*/
protected $actual = null;
/**
* @var int
*/
protected $expected = 0;
/**
* @var string
*/
protected $expectedComparative = '<=';
/**
* @var string|null
*/
protected $method = null;
/**
* @var LegacyMockInterface|null
*/
protected $mockObject = null;
/**
* @return int|null
*/
public function getActualCount()
{
return $this->actual;
}
/**
* @return int
*/
public function getExpectedCount()
{
return $this->expected;
}
/**
* @return string
*/
public function getExpectedCountComparative()
{
return $this->expectedComparative;
}
/**
* @return string|null
*/
public function getMethodName()
{
return $this->method;
}
/**
* @return LegacyMockInterface|null
*/
public function getMock()
{
return $this->mockObject;
}
/**
* @throws RuntimeException
* @return string|null
*/
public function getMockName()
{
$mock = $this->getMock();
if ($mock === null) {
return '';
}
return $mock->mockery_getName();
}
/**
* @param int $count
* @return self
*/
public function setActualCount($count)
{
$this->actual = $count;
return $this;
}
/**
* @param int $count
* @return self
*/
public function setExpectedCount($count)
{
$this->expected = $count;
return $this;
}
/**
* @param string $comp
* @return self
*/
public function setExpectedCountComparative($comp)
{
if (! in_array($comp, ['=', '>', '<', '>=', '<='], true)) {
throw new RuntimeException('Illegal comparative for expected call counts set: ' . $comp);
}
$this->expectedComparative = $comp;
return $this;
}
/**
* @param string $name
* @return self
*/
public function setMethodName($name)
{
$this->method = $name;
return $this;
}
/**
* @return self
*/
public function setMock(LegacyMockInterface $mock)
{
$this->mockObject = $mock;
return $this;
}
}

View File

@@ -0,0 +1,125 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Exception;
use Mockery\Exception;
use Mockery\LegacyMockInterface;
class InvalidOrderException extends Exception
{
/**
* @var int|null
*/
protected $actual = null;
/**
* @var int
*/
protected $expected = 0;
/**
* @var string|null
*/
protected $method = null;
/**
* @var LegacyMockInterface|null
*/
protected $mockObject = null;
/**
* @return int|null
*/
public function getActualOrder()
{
return $this->actual;
}
/**
* @return int
*/
public function getExpectedOrder()
{
return $this->expected;
}
/**
* @return string|null
*/
public function getMethodName()
{
return $this->method;
}
/**
* @return LegacyMockInterface|null
*/
public function getMock()
{
return $this->mockObject;
}
/**
* @return string|null
*/
public function getMockName()
{
$mock = $this->getMock();
if ($mock === null) {
return $mock;
}
return $mock->mockery_getName();
}
/**
* @param int $count
*
* @return self
*/
public function setActualOrder($count)
{
$this->actual = $count;
return $this;
}
/**
* @param int $count
*
* @return self
*/
public function setExpectedOrder($count)
{
$this->expected = $count;
return $this;
}
/**
* @param string $name
*
* @return self
*/
public function setMethodName($name)
{
$this->method = $name;
return $this;
}
/**
* @return self
*/
public function setMock(LegacyMockInterface $mock)
{
$this->mockObject = $mock;
return $this;
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
declare(strict_types=1);
namespace Mockery\Exception;
use Throwable;
interface MockeryExceptionInterface extends Throwable
{
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Exception;
use Mockery\Exception;
use Mockery\LegacyMockInterface;
class NoMatchingExpectationException extends Exception
{
/**
* @var array<mixed>
*/
protected $actual = [];
/**
* @var string|null
*/
protected $method = null;
/**
* @var LegacyMockInterface|null
*/
protected $mockObject = null;
/**
* @return array<mixed>
*/
public function getActualArguments()
{
return $this->actual;
}
/**
* @return string|null
*/
public function getMethodName()
{
return $this->method;
}
/**
* @return LegacyMockInterface|null
*/
public function getMock()
{
return $this->mockObject;
}
/**
* @return string|null
*/
public function getMockName()
{
$mock = $this->getMock();
if ($mock === null) {
return $mock;
}
return $mock->mockery_getName();
}
/**
* @todo Rename param `count` to `args`
* @template TMixed
*
* @param array<TMixed> $count
* @return self
*/
public function setActualArguments($count)
{
$this->actual = $count;
return $this;
}
/**
* @param string $name
* @return self
*/
public function setMethodName($name)
{
$this->method = $name;
return $this;
}
/**
* @return self
*/
public function setMock(LegacyMockInterface $mock)
{
$this->mockObject = $mock;
return $this;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Exception;
use Exception;
class RuntimeException extends Exception implements MockeryExceptionInterface
{
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,242 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use Mockery;
use Mockery\Exception\NoMatchingExpectationException;
use function array_pop;
use function array_unshift;
use function end;
use const PHP_EOL;
class ExpectationDirector
{
/**
* Stores an array of all default expectations for this mock
*
* @var list<ExpectationInterface>
*/
protected $_defaults = [];
/**
* Stores an array of all expectations for this mock
*
* @var list<ExpectationInterface>
*/
protected $_expectations = [];
/**
* The expected order of next call
*
* @var int
*/
protected $_expectedOrder = null;
/**
* Mock object the director is attached to
*
* @var LegacyMockInterface|MockInterface
*/
protected $_mock = null;
/**
* Method name the director is directing
*
* @var string
*/
protected $_name = null;
/**
* Constructor
*
* @param string $name
*/
public function __construct($name, LegacyMockInterface $mock)
{
$this->_name = $name;
$this->_mock = $mock;
}
/**
* Add a new expectation to the director
*/
public function addExpectation(Expectation $expectation)
{
$this->_expectations[] = $expectation;
}
/**
* Handle a method call being directed by this instance
*
* @return mixed
*/
public function call(array $args)
{
$expectation = $this->findExpectation($args);
if ($expectation !== null) {
return $expectation->verifyCall($args);
}
$exception = new NoMatchingExpectationException(
'No matching handler found for '
. $this->_mock->mockery_getName() . '::'
. Mockery::formatArgs($this->_name, $args)
. '. Either the method was unexpected or its arguments matched'
. ' no expected argument list for this method'
. PHP_EOL . PHP_EOL
. Mockery::formatObjects($args)
);
$exception->setMock($this->_mock)
->setMethodName($this->_name)
->setActualArguments($args);
throw $exception;
}
/**
* Attempt to locate an expectation matching the provided args
*
* @return mixed
*/
public function findExpectation(array $args)
{
$expectation = null;
if ($this->_expectations !== []) {
$expectation = $this->_findExpectationIn($this->_expectations, $args);
}
if ($expectation === null && $this->_defaults !== []) {
return $this->_findExpectationIn($this->_defaults, $args);
}
return $expectation;
}
/**
* Return all expectations assigned to this director
*
* @return array<ExpectationInterface>
*/
public function getDefaultExpectations()
{
return $this->_defaults;
}
/**
* Return the number of expectations assigned to this director.
*
* @return int
*/
public function getExpectationCount()
{
$count = 0;
$expectations = $this->getExpectations();
if ($expectations === []) {
$expectations = $this->getDefaultExpectations();
}
foreach ($expectations as $expectation) {
if ($expectation->isCallCountConstrained()) {
++$count;
}
}
return $count;
}
/**
* Return all expectations assigned to this director
*
* @return array<ExpectationInterface>
*/
public function getExpectations()
{
return $this->_expectations;
}
/**
* Make the given expectation a default for all others assuming it was correctly created last
*
* @throws Exception
*
* @return void
*/
public function makeExpectationDefault(Expectation $expectation)
{
if (end($this->_expectations) === $expectation) {
array_pop($this->_expectations);
array_unshift($this->_defaults, $expectation);
return;
}
throw new Exception('Cannot turn a previously defined expectation into a default');
}
/**
* Verify all expectations of the director
*
* @throws Exception
*
* @return void
*/
public function verify()
{
if ($this->_expectations !== []) {
foreach ($this->_expectations as $expectation) {
$expectation->verify();
}
return;
}
foreach ($this->_defaults as $expectation) {
$expectation->verify();
}
}
/**
* Search current array of expectations for a match
*
* @param array<ExpectationInterface> $expectations
*
* @return null|ExpectationInterface
*/
protected function _findExpectationIn(array $expectations, array $args)
{
foreach ($expectations as $expectation) {
if (! $expectation->isEligible()) {
continue;
}
if (! $expectation->matchArgs($args)) {
continue;
}
return $expectation;
}
foreach ($expectations as $expectation) {
if ($expectation->matchArgs($args)) {
return $expectation;
}
}
return null;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
interface ExpectationInterface
{
/**
* @template TArgs
*
* @param TArgs ...$args
*
* @return self
*/
public function andReturn(...$args);
/**
* @return self
*/
public function andReturns();
/**
* @return LegacyMockInterface|MockInterface
*/
public function getMock();
/**
* @return int
*/
public function getOrderNumber();
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
class ExpectsHigherOrderMessage extends HigherOrderMessage
{
public function __construct(MockInterface $mock)
{
parent::__construct($mock, 'shouldReceive');
}
/**
* @param string $method
* @param array $args
*
* @return Expectation|ExpectationInterface|HigherOrderMessage
*/
public function __call($method, $args)
{
$expectation = parent::__call($method, $args);
return $expectation->once();
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
class CachingGenerator implements Generator
{
/**
* @var array<string,string>
*/
protected $cache = [];
/**
* @var Generator
*/
protected $generator;
public function __construct(Generator $generator)
{
$this->generator = $generator;
}
/**
* @return string
*/
public function generate(MockConfiguration $config)
{
$hash = $config->getHash();
if (array_key_exists($hash, $this->cache)) {
return $this->cache[$hash];
}
return $this->cache[$hash] = $this->generator->generate($config);
}
}

View File

@@ -0,0 +1,188 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionMethod;
use function array_map;
use function array_merge;
use function array_unique;
use const PHP_VERSION_ID;
class DefinedTargetClass implements TargetClassInterface
{
/**
* @var class-string
*/
private $name;
/**
* @var ReflectionClass
*/
private $rfc;
/**
* @param ReflectionClass $rfc
* @param class-string|null $alias
*/
public function __construct(ReflectionClass $rfc, $alias = null)
{
$this->rfc = $rfc;
$this->name = $alias ?? $rfc->getName();
}
/**
* @return class-string
*/
public function __toString()
{
return $this->name;
}
/**
* @param class-string $name
* @param class-string|null $alias
* @return self
*/
public static function factory($name, $alias = null)
{
return new self(new ReflectionClass($name), $alias);
}
/**
* @return list<class-string>
*/
public function getAttributes()
{
if (PHP_VERSION_ID < 80000) {
return [];
}
return array_unique(
array_merge(
['\AllowDynamicProperties'],
array_map(
static function (ReflectionAttribute $attribute): string {
return '\\' . $attribute->getName();
},
$this->rfc->getAttributes()
)
)
);
}
/**
* @return array<class-string,self>
*/
public function getInterfaces()
{
return array_map(
static function (ReflectionClass $interface): self {
return new self($interface);
},
$this->rfc->getInterfaces()
);
}
/**
* @return list<Method>
*/
public function getMethods()
{
return array_map(
static function (ReflectionMethod $method): Method {
return new Method($method);
},
$this->rfc->getMethods()
);
}
/**
* @return class-string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getNamespaceName()
{
return $this->rfc->getNamespaceName();
}
/**
* @return string
*/
public function getShortName()
{
return $this->rfc->getShortName();
}
/**
* @return bool
*/
public function hasInternalAncestor()
{
if ($this->rfc->isInternal()) {
return true;
}
$child = $this->rfc;
while ($parent = $child->getParentClass()) {
if ($parent->isInternal()) {
return true;
}
$child = $parent;
}
return false;
}
/**
* @param class-string $interface
* @return bool
*/
public function implementsInterface($interface)
{
return $this->rfc->implementsInterface($interface);
}
/**
* @return bool
*/
public function inNamespace()
{
return $this->rfc->inNamespace();
}
/**
* @return bool
*/
public function isAbstract()
{
return $this->rfc->isAbstract();
}
/**
* @return bool
*/
public function isFinal()
{
return $this->rfc->isFinal();
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
interface Generator
{
/**
* @returns MockDefinition
*/
public function generate(MockConfiguration $config);
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
use Mockery\Reflector;
use ReflectionMethod;
use ReflectionParameter;
use function array_map;
/**
* @mixin ReflectionMethod
*/
class Method
{
/**
* @var ReflectionMethod
*/
private $method;
public function __construct(ReflectionMethod $method)
{
$this->method = $method;
}
/**
* @template TArgs
* @template TMixed
*
* @param string $method
* @param array<TArgs> $args
*
* @return TMixed
*/
public function __call($method, $args)
{
/** @var TMixed */
return $this->method->{$method}(...$args);
}
/**
* @return list<Parameter>
*/
public function getParameters()
{
return array_map(static function (ReflectionParameter $parameter) {
return new Parameter($parameter);
}, $this->method->getParameters());
}
/**
* @return null|string
*/
public function getReturnType()
{
return Reflector::getReturnType($this->method);
}
}

View File

@@ -0,0 +1,709 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
use Mockery\Exception;
use Serializable;
use function array_filter;
use function array_keys;
use function array_map;
use function array_merge;
use function array_pop;
use function array_unique;
use function array_values;
use function class_alias;
use function class_exists;
use function explode;
use function get_class;
use function implode;
use function in_array;
use function interface_exists;
use function is_object;
use function md5;
use function preg_match;
use function serialize;
use function strpos;
use function strtolower;
use function trait_exists;
/**
* This class describes the configuration of mocks and hides away some of the
* reflection implementation
*/
class MockConfiguration
{
/**
* Instance cache of all methods
*
* @var list<Method>
*/
protected $allMethods = [];
/**
* Methods that should specifically not be mocked
*
* This is currently populated with stuff we don't know how to deal with, should really be somewhere else
*/
protected $blackListedMethods = [];
protected $constantsMap = [];
/**
* An instance mock is where we override the original class before it's autoloaded
*
* @var bool
*/
protected $instanceMock = false;
/**
* If true, overrides original class destructor
*
* @var bool
*/
protected $mockOriginalDestructor = false;
/**
* The class name we'd like to use for a generated mock
*
* @var string|null
*/
protected $name;
/**
* Param overrides
*
* @var array<string,mixed>
*/
protected $parameterOverrides = [];
/**
* A class that we'd like to mock
* @var TargetClassInterface|null
*/
protected $targetClass;
/**
* @var class-string|null
*/
protected $targetClassName;
/**
* @var array<class-string>
*/
protected $targetInterfaceNames = [];
/**
* A number of interfaces we'd like to mock, keyed by name to attempt to keep unique
*
* @var array<TargetClassInterface>
*/
protected $targetInterfaces = [];
/**
* An object we'd like our mock to proxy to
*
* @var object|null
*/
protected $targetObject;
/**
* @var array<string>
*/
protected $targetTraitNames = [];
/**
* A number of traits we'd like to mock, keyed by name to attempt to keep unique
*
* @var array<string,DefinedTargetClass>
*/
protected $targetTraits = [];
/**
* If not empty, only these methods will be mocked
*
* @var array<string>
*/
protected $whiteListedMethods = [];
/**
* @param array<class-string|object> $targets
* @param array<string> $blackListedMethods
* @param array<string> $whiteListedMethods
* @param string|null $name
* @param bool $instanceMock
* @param array<string,mixed> $parameterOverrides
* @param bool $mockOriginalDestructor
* @param array<string,array<scalar>|scalar> $constantsMap
*/
public function __construct(
array $targets = [],
array $blackListedMethods = [],
array $whiteListedMethods = [],
$name = null,
$instanceMock = false,
array $parameterOverrides = [],
$mockOriginalDestructor = false,
array $constantsMap = []
) {
$this->addTargets($targets);
$this->blackListedMethods = $blackListedMethods;
$this->whiteListedMethods = $whiteListedMethods;
$this->name = $name;
$this->instanceMock = $instanceMock;
$this->parameterOverrides = $parameterOverrides;
$this->mockOriginalDestructor = $mockOriginalDestructor;
$this->constantsMap = $constantsMap;
}
/**
* Generate a suitable name based on the config
*
* @return string
*/
public function generateName()
{
$nameBuilder = new MockNameBuilder();
$targetObject = $this->getTargetObject();
if ($targetObject !== null) {
$className = get_class($targetObject);
$nameBuilder->addPart(strpos($className, '@') !== false ? md5($className) : $className);
}
$targetClass = $this->getTargetClass();
if ($targetClass instanceof TargetClassInterface) {
$className = $targetClass->getName();
$nameBuilder->addPart(strpos($className, '@') !== false ? md5($className) : $className);
}
foreach ($this->getTargetInterfaces() as $targetInterface) {
$nameBuilder->addPart($targetInterface->getName());
}
return $nameBuilder->build();
}
/**
* @return array<string>
*/
public function getBlackListedMethods()
{
return $this->blackListedMethods;
}
/**
* @return array<string,scalar|array<scalar>>
*/
public function getConstantsMap()
{
return $this->constantsMap;
}
/**
* Attempt to create a hash of the configuration, in order to allow caching
*
* @TODO workout if this will work
*
* @return string
*/
public function getHash()
{
$vars = [
'targetClassName' => $this->targetClassName,
'targetInterfaceNames' => $this->targetInterfaceNames,
'targetTraitNames' => $this->targetTraitNames,
'name' => $this->name,
'blackListedMethods' => $this->blackListedMethods,
'whiteListedMethod' => $this->whiteListedMethods,
'instanceMock' => $this->instanceMock,
'parameterOverrides' => $this->parameterOverrides,
'mockOriginalDestructor' => $this->mockOriginalDestructor,
];
return md5(serialize($vars));
}
/**
* Gets a list of methods from the classes, interfaces and objects and filters them appropriately.
* Lot's of filtering going on, perhaps we could have filter classes to iterate through
*
* @return list<Method>
*/
public function getMethodsToMock()
{
$methods = $this->getAllMethods();
foreach ($methods as $key => $method) {
if ($method->isFinal()) {
unset($methods[$key]);
}
}
/**
* Whitelist trumps everything else
*/
$whiteListedMethods = $this->getWhiteListedMethods();
if ($whiteListedMethods !== []) {
$whitelist = array_map('strtolower', $whiteListedMethods);
return array_filter($methods, static function ($method) use ($whitelist) {
if ($method->isAbstract()) {
return true;
}
return in_array(strtolower($method->getName()), $whitelist, true);
});
}
/**
* Remove blacklisted methods
*/
$blackListedMethods = $this->getBlackListedMethods();
if ($blackListedMethods !== []) {
$blacklist = array_map('strtolower', $blackListedMethods);
$methods = array_filter($methods, static function ($method) use ($blacklist) {
return ! in_array(strtolower($method->getName()), $blacklist, true);
});
}
/**
* Internal objects can not be instantiated with newInstanceArgs and if
* they implement Serializable, unserialize will have to be called. As
* such, we can't mock it and will need a pass to add a dummy
* implementation
*/
$targetClass = $this->getTargetClass();
if (
$targetClass !== null
&& $targetClass->implementsInterface(Serializable::class)
&& $targetClass->hasInternalAncestor()
) {
$methods = array_filter($methods, static function ($method) {
return $method->getName() !== 'unserialize';
});
}
return array_values($methods);
}
/**
* @return string|null
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getNamespaceName()
{
$parts = explode('\\', $this->getName());
array_pop($parts);
if ($parts !== []) {
return implode('\\', $parts);
}
return '';
}
/**
* @return array<string,mixed>
*/
public function getParameterOverrides()
{
return $this->parameterOverrides;
}
/**
* @return string
*/
public function getShortName()
{
$parts = explode('\\', $this->getName());
return array_pop($parts);
}
/**
* @return null|TargetClassInterface
*/
public function getTargetClass()
{
if ($this->targetClass) {
return $this->targetClass;
}
if (! $this->targetClassName) {
return null;
}
if (class_exists($this->targetClassName)) {
$alias = null;
if (strpos($this->targetClassName, '@') !== false) {
$alias = (new MockNameBuilder())
->addPart('anonymous_class')
->addPart(md5($this->targetClassName))
->build();
class_alias($this->targetClassName, $alias);
}
$dtc = DefinedTargetClass::factory($this->targetClassName, $alias);
if ($this->getTargetObject() === null && $dtc->isFinal()) {
throw new Exception(
'The class ' . $this->targetClassName . ' is marked final and its methods'
. ' cannot be replaced. Classes marked final can be passed in'
. ' to \Mockery::mock() as instantiated objects to create a'
. ' partial mock, but only if the mock is not subject to type'
. ' hinting checks.'
);
}
$this->targetClass = $dtc;
} else {
$this->targetClass = UndefinedTargetClass::factory($this->targetClassName);
}
return $this->targetClass;
}
/**
* @return class-string|null
*/
public function getTargetClassName()
{
return $this->targetClassName;
}
/**
* @return list<TargetClassInterface>
*/
public function getTargetInterfaces()
{
if ($this->targetInterfaces !== []) {
return $this->targetInterfaces;
}
foreach ($this->targetInterfaceNames as $targetInterface) {
if (! interface_exists($targetInterface)) {
$this->targetInterfaces[] = UndefinedTargetClass::factory($targetInterface);
continue;
}
$dtc = DefinedTargetClass::factory($targetInterface);
$extendedInterfaces = array_keys($dtc->getInterfaces());
$extendedInterfaces[] = $targetInterface;
$traversableFound = false;
$iteratorShiftedToFront = false;
foreach ($extendedInterfaces as $interface) {
if (! $traversableFound && preg_match('/^\\?Iterator(|Aggregate)$/i', $interface)) {
break;
}
if (preg_match('/^\\\\?IteratorAggregate$/i', $interface)) {
$this->targetInterfaces[] = DefinedTargetClass::factory('\\IteratorAggregate');
$iteratorShiftedToFront = true;
continue;
}
if (preg_match('/^\\\\?Iterator$/i', $interface)) {
$this->targetInterfaces[] = DefinedTargetClass::factory('\\Iterator');
$iteratorShiftedToFront = true;
continue;
}
if (preg_match('/^\\\\?Traversable$/i', $interface)) {
$traversableFound = true;
}
}
if ($traversableFound && ! $iteratorShiftedToFront) {
$this->targetInterfaces[] = DefinedTargetClass::factory('\\IteratorAggregate');
}
/**
* We never straight up implement Traversable
*/
$isTraversable = preg_match('/^\\\\?Traversable$/i', $targetInterface);
if ($isTraversable === 0 || $isTraversable === false) {
$this->targetInterfaces[] = $dtc;
}
}
return $this->targetInterfaces = array_unique($this->targetInterfaces);
}
/**
* @return object|null
*/
public function getTargetObject()
{
return $this->targetObject;
}
/**
* @return list<TargetClassInterface>
*/
public function getTargetTraits()
{
if ($this->targetTraits !== []) {
return $this->targetTraits;
}
foreach ($this->targetTraitNames as $targetTrait) {
$this->targetTraits[] = DefinedTargetClass::factory($targetTrait);
}
$this->targetTraits = array_unique($this->targetTraits); // just in case
return $this->targetTraits;
}
/**
* @return array<string>
*/
public function getWhiteListedMethods()
{
return $this->whiteListedMethods;
}
/**
* @return bool
*/
public function isInstanceMock()
{
return $this->instanceMock;
}
/**
* @return bool
*/
public function isMockOriginalDestructor()
{
return $this->mockOriginalDestructor;
}
/**
* @param class-string $className
* @return self
*/
public function rename($className)
{
$targets = [];
if ($this->targetClassName) {
$targets[] = $this->targetClassName;
}
if ($this->targetInterfaceNames) {
$targets = array_merge($targets, $this->targetInterfaceNames);
}
if ($this->targetTraitNames) {
$targets = array_merge($targets, $this->targetTraitNames);
}
if ($this->targetObject) {
$targets[] = $this->targetObject;
}
return new self(
$targets,
$this->blackListedMethods,
$this->whiteListedMethods,
$className,
$this->instanceMock,
$this->parameterOverrides,
$this->mockOriginalDestructor,
$this->constantsMap
);
}
/**
* We declare the __callStatic method to handle undefined stuff, if the class
* we're mocking has also defined it, we need to comply with their interface
*
* @return bool
*/
public function requiresCallStaticTypeHintRemoval()
{
foreach ($this->getAllMethods() as $method) {
if ($method->getName() === '__callStatic') {
$params = $method->getParameters();
if (! array_key_exists(1, $params)) {
return false;
}
return ! $params[1]->isArray();
}
}
return false;
}
/**
* We declare the __call method to handle undefined stuff, if the class
* we're mocking has also defined it, we need to comply with their interface
*
* @return bool
*/
public function requiresCallTypeHintRemoval()
{
foreach ($this->getAllMethods() as $method) {
if ($method->getName() === '__call') {
$params = $method->getParameters();
return ! $params[1]->isArray();
}
}
return false;
}
/**
* @param class-string|object $target
*/
protected function addTarget($target)
{
if (is_object($target)) {
$this->setTargetObject($target);
$this->setTargetClassName(get_class($target));
return;
}
if ($target[0] !== '\\') {
$target = '\\' . $target;
}
if (class_exists($target)) {
$this->setTargetClassName($target);
return;
}
if (interface_exists($target)) {
$this->addTargetInterfaceName($target);
return;
}
if (trait_exists($target)) {
$this->addTargetTraitName($target);
return;
}
/**
* Default is to set as class, or interface if class already set
*
* Don't like this condition, can't remember what the default
* targetClass is for
*/
if ($this->getTargetClassName()) {
$this->addTargetInterfaceName($target);
return;
}
$this->setTargetClassName($target);
}
/**
* If we attempt to implement Traversable,
* we must ensure we are also implementing either Iterator or IteratorAggregate,
* and that whichever one it is comes before Traversable in the list of implements.
*
* @param class-string $targetInterface
*/
protected function addTargetInterfaceName($targetInterface)
{
$this->targetInterfaceNames[] = $targetInterface;
}
/**
* @param array<class-string> $interfaces
*/
protected function addTargets($interfaces)
{
foreach ($interfaces as $interface) {
$this->addTarget($interface);
}
}
/**
* @param class-string $targetTraitName
*/
protected function addTargetTraitName($targetTraitName)
{
$this->targetTraitNames[] = $targetTraitName;
}
/**
* @return list<Method>
*/
protected function getAllMethods()
{
if ($this->allMethods) {
return $this->allMethods;
}
$classes = $this->getTargetInterfaces();
if ($this->getTargetClass()) {
$classes[] = $this->getTargetClass();
}
$methods = [];
foreach ($classes as $class) {
$methods = array_merge($methods, $class->getMethods());
}
foreach ($this->getTargetTraits() as $trait) {
foreach ($trait->getMethods() as $method) {
if ($method->isAbstract()) {
$methods[] = $method;
}
}
}
$names = [];
$methods = array_filter($methods, static function ($method) use (&$names) {
if (in_array($method->getName(), $names, true)) {
return false;
}
$names[] = $method->getName();
return true;
});
return $this->allMethods = $methods;
}
/**
* @param class-string $targetClassName
*/
protected function setTargetClassName($targetClassName)
{
$this->targetClassName = $targetClassName;
}
/**
* @param object $object
*/
protected function setTargetObject($object)
{
$this->targetObject = $object;
}
}

View File

@@ -0,0 +1,252 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
use function array_diff;
class MockConfigurationBuilder
{
/**
* @var list<string>
*/
protected $blackListedMethods = [
'__call',
'__callStatic',
'__clone',
'__wakeup',
'__set',
'__get',
'__toString',
'__isset',
'__destruct',
'__debugInfo', ## mocking this makes it difficult to debug with xdebug
// below are reserved words in PHP
'__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',
];
/**
* @var array
*/
protected $constantsMap = [];
/**
* @var bool
*/
protected $instanceMock = false;
/**
* @var bool
*/
protected $mockOriginalDestructor = false;
/**
* @var string
*/
protected $name;
/**
* @var array
*/
protected $parameterOverrides = [];
/**
* @var list<string>
*/
protected $php7SemiReservedKeywords = [
'callable', 'class', 'trait', 'extends', 'implements', 'static', 'abstract', 'final',
'public', 'protected', 'private', 'const', 'enddeclare', 'endfor', 'endforeach', 'endif',
'endwhile', 'and', 'global', 'goto', 'instanceof', 'insteadof', 'interface', 'namespace', 'new',
'or', 'xor', 'try', 'use', 'var', 'exit', 'list', 'clone', 'include', 'include_once', 'throw',
'array', 'print', 'echo', 'require', 'require_once', 'return', 'else', 'elseif', 'default',
'break', 'continue', 'switch', 'yield', 'function', 'if', 'endswitch', 'finally', 'for', 'foreach',
'declare', 'case', 'do', 'while', 'as', 'catch', 'die', 'self', 'parent',
];
/**
* @var array
*/
protected $targets = [];
/**
* @var array
*/
protected $whiteListedMethods = [];
public function __construct()
{
$this->blackListedMethods = array_diff($this->blackListedMethods, $this->php7SemiReservedKeywords);
}
/**
* @param string $blackListedMethod
* @return self
*/
public function addBlackListedMethod($blackListedMethod)
{
$this->blackListedMethods[] = $blackListedMethod;
return $this;
}
/**
* @param list<string> $blackListedMethods
* @return self
*/
public function addBlackListedMethods(array $blackListedMethods)
{
foreach ($blackListedMethods as $method) {
$this->addBlackListedMethod($method);
}
return $this;
}
/**
* @param class-string $target
* @return self
*/
public function addTarget($target)
{
$this->targets[] = $target;
return $this;
}
/**
* @param list<class-string> $targets
* @return self
*/
public function addTargets($targets)
{
foreach ($targets as $target) {
$this->addTarget($target);
}
return $this;
}
/**
* @return self
*/
public function addWhiteListedMethod($whiteListedMethod)
{
$this->whiteListedMethods[] = $whiteListedMethod;
return $this;
}
/**
* @return self
*/
public function addWhiteListedMethods(array $whiteListedMethods)
{
foreach ($whiteListedMethods as $method) {
$this->addWhiteListedMethod($method);
}
return $this;
}
/**
* @return MockConfiguration
*/
public function getMockConfiguration()
{
return new MockConfiguration(
$this->targets,
$this->blackListedMethods,
$this->whiteListedMethods,
$this->name,
$this->instanceMock,
$this->parameterOverrides,
$this->mockOriginalDestructor,
$this->constantsMap
);
}
/**
* @param list<string> $blackListedMethods
* @return self
*/
public function setBlackListedMethods(array $blackListedMethods)
{
$this->blackListedMethods = $blackListedMethods;
return $this;
}
/**
* @return self
*/
public function setConstantsMap(array $map)
{
$this->constantsMap = $map;
return $this;
}
/**
* @param bool $instanceMock
*/
public function setInstanceMock($instanceMock)
{
$this->instanceMock = (bool) $instanceMock;
return $this;
}
/**
* @param bool $mockDestructor
*/
public function setMockOriginalDestructor($mockDestructor)
{
$this->mockOriginalDestructor = (bool) $mockDestructor;
return $this;
}
/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* @return self
*/
public function setParameterOverrides(array $overrides)
{
$this->parameterOverrides = $overrides;
return $this;
}
/**
* @param list<string> $whiteListedMethods
* @return self
*/
public function setWhiteListedMethods(array $whiteListedMethods)
{
$this->whiteListedMethods = $whiteListedMethods;
return $this;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
use InvalidArgumentException;
class MockDefinition
{
/**
* @var string
*/
protected $code;
/**
* @var MockConfiguration
*/
protected $config;
/**
* @param string $code
* @throws InvalidArgumentException
*/
public function __construct(MockConfiguration $config, $code)
{
if (! $config->getName()) {
throw new InvalidArgumentException('MockConfiguration must contain a name');
}
$this->config = $config;
$this->code = $code;
}
/**
* @return string
*/
public function getClassName()
{
return $this->config->getName();
}
/**
* @return string
*/
public function getCode()
{
return $this->code;
}
/**
* @return MockConfiguration
*/
public function getConfig()
{
return $this->config;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
use function implode;
use function str_replace;
class MockNameBuilder
{
/**
* @var int
*/
protected static $mockCounter = 0;
/**
* @var list<string>
*/
protected $parts = [];
/**
* @param string $part
*/
public function addPart($part)
{
$this->parts[] = $part;
return $this;
}
/**
* @return string
*/
public function build()
{
$parts = ['Mockery', static::$mockCounter++];
foreach ($this->parts as $part) {
$parts[] = str_replace('\\', '_', $part);
}
return implode('_', $parts);
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
use Mockery\Reflector;
use ReflectionClass;
use ReflectionParameter;
use function class_exists;
/**
* @mixin ReflectionParameter
*/
class Parameter
{
/**
* @var int
*/
private static $parameterCounter = 0;
/**
* @var ReflectionParameter
*/
private $rfp;
public function __construct(ReflectionParameter $rfp)
{
$this->rfp = $rfp;
}
/**
* Proxy all method calls to the reflection parameter.
*
* @template TMixed
* @template TResult
*
* @param string $method
* @param array<TMixed> $args
*
* @return TResult
*/
public function __call($method, array $args)
{
/** @var TResult */
return $this->rfp->{$method}(...$args);
}
/**
* Get the reflection class for the parameter type, if it exists.
*
* This will be null if there was no type, or it was a scalar or a union.
*
* @return null|ReflectionClass
*
* @deprecated since 1.3.3 and will be removed in 2.0.
*/
public function getClass()
{
$typeHint = Reflector::getTypeHint($this->rfp, true);
return class_exists($typeHint) ? DefinedTargetClass::factory($typeHint, false) : null;
}
/**
* Get the name of the parameter.
*
* Some internal classes have funny looking definitions!
*
* @return string
*/
public function getName()
{
$name = $this->rfp->getName();
if (! $name || $name === '...') {
return 'arg' . self::$parameterCounter++;
}
return $name;
}
/**
* Get the string representation for the paramater type.
*
* @return null|string
*/
public function getTypeHint()
{
return Reflector::getTypeHint($this->rfp);
}
/**
* Get the string representation for the paramater type.
*
* @return string
*
* @deprecated since 1.3.2 and will be removed in 2.0. Use getTypeHint() instead.
*/
public function getTypeHintAsString()
{
return (string) Reflector::getTypeHint($this->rfp, true);
}
/**
* Determine if the parameter is an array.
*
* @return bool
*/
public function isArray()
{
return Reflector::isArray($this->rfp);
}
/**
* Determine if the parameter is variadic.
*
* @return bool
*/
public function isVariadic()
{
return $this->rfp->isVariadic();
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use function array_map;
use function in_array;
use function preg_replace;
use function sprintf;
use function str_replace;
class AvoidMethodClashPass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$names = array_map(static function ($method) {
return $method->getName();
}, $config->getMethodsToMock());
foreach (['allows', 'expects'] as $method) {
if (in_array($method, $names, true)) {
$code = preg_replace(sprintf('#// start method %s.*// end method %s#ms', $method, $method), '', $code);
$code = str_replace(' implements MockInterface', ' implements LegacyMockInterface', $code);
}
}
return $code;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use function str_replace;
class CallTypeHintPass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
if ($config->requiresCallTypeHintRemoval()) {
$code = str_replace(
'public function __call($method, array $args)',
'public function __call($method, $args)',
$code
);
}
if ($config->requiresCallStaticTypeHintRemoval()) {
return str_replace(
'public static function __callStatic($method, array $args)',
'public static function __callStatic($method, $args)',
$code
);
}
return $code;
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use function implode;
use function str_replace;
class ClassAttributesPass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$class = $config->getTargetClass();
if (! $class) {
return $code;
}
/** @var array<string> $attributes */
$attributes = $class->getAttributes();
if ($attributes !== []) {
return str_replace('#[\AllowDynamicProperties]', '#[' . implode(',', $attributes) . ']', $code);
}
return $code;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use function ltrim;
use function str_replace;
class ClassNamePass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$namespace = $config->getNamespaceName();
$namespace = ltrim($namespace, '\\');
$className = $config->getShortName();
$code = str_replace('namespace Mockery;', $namespace !== '' ? 'namespace ' . $namespace . ';' : '', $code);
return str_replace('class Mock', 'class ' . $className, $code);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery;
use Mockery\Generator\MockConfiguration;
use function class_exists;
use function ltrim;
use function str_replace;
class ClassPass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$target = $config->getTargetClass();
if (! $target) {
return $code;
}
if ($target->isFinal()) {
return $code;
}
$className = ltrim($target->getName(), '\\');
if (! class_exists($className)) {
Mockery::declareClass($className);
}
return str_replace(
'implements MockInterface',
'extends \\' . $className . ' implements MockInterface',
$code
);
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use function array_key_exists;
use function sprintf;
use function strrpos;
use function substr_replace;
use function var_export;
use const PHP_EOL;
class ConstantsPass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$cm = $config->getConstantsMap();
if ($cm === []) {
return $code;
}
$name = $config->getName();
if (! array_key_exists($name, $cm)) {
return $code;
}
$constantsCode = '';
foreach ($cm[$name] as $constant => $value) {
$constantsCode .= sprintf("\n const %s = %s;\n", $constant, var_export($value, true));
}
$offset = strrpos($code, '}');
if ($offset === false) {
return $code;
}
return substr_replace($code, $constantsCode, $offset) . '}' . PHP_EOL;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use function strrpos;
use function substr;
class InstanceMockPass implements Pass
{
public const INSTANCE_MOCK_CODE = <<<MOCK
protected \$_mockery_ignoreVerification = true;
public function __construct()
{
\$this->_mockery_ignoreVerification = false;
\$associatedRealObject = \Mockery::fetchMock(__CLASS__);
foreach (get_object_vars(\$this) as \$attr => \$val) {
if (\$attr !== "_mockery_ignoreVerification" && \$attr !== "_mockery_expectations") {
\$this->\$attr = \$associatedRealObject->\$attr;
}
}
\$directors = \$associatedRealObject->mockery_getExpectations();
foreach (\$directors as \$method=>\$director) {
// get the director method needed
\$existingDirector = \$this->mockery_getExpectationsFor(\$method);
if (!\$existingDirector) {
\$existingDirector = new \Mockery\ExpectationDirector(\$method, \$this);
\$this->mockery_setExpectationsFor(\$method, \$existingDirector);
}
\$expectations = \$director->getExpectations();
foreach (\$expectations as \$expectation) {
\$clonedExpectation = clone \$expectation;
\$existingDirector->addExpectation(\$clonedExpectation);
}
\$defaultExpectations = \$director->getDefaultExpectations();
foreach (array_reverse(\$defaultExpectations) as \$expectation) {
\$clonedExpectation = clone \$expectation;
\$existingDirector->addExpectation(\$clonedExpectation);
\$existingDirector->makeExpectationDefault(\$clonedExpectation);
}
}
\Mockery::getContainer()->rememberMock(\$this);
\$this->_mockery_constructorCalled(func_get_args());
}
MOCK;
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
if ($config->isInstanceMock()) {
return $this->appendToClass($code, static::INSTANCE_MOCK_CODE);
}
return $code;
}
protected function appendToClass($class, $code)
{
$lastBrace = strrpos($class, '}');
return substr($class, 0, $lastBrace) . $code . "\n }\n";
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery;
use Mockery\Generator\MockConfiguration;
use function array_reduce;
use function interface_exists;
use function ltrim;
use function str_replace;
class InterfacePass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
foreach ($config->getTargetInterfaces() as $i) {
$name = ltrim($i->getName(), '\\');
if (! interface_exists($name)) {
Mockery::declareInterface($name);
}
}
$interfaces = array_reduce($config->getTargetInterfaces(), static function ($code, $i) {
return $code . ', \\' . ltrim($i->getName(), '\\');
}, '');
return str_replace('implements MockInterface', 'implements MockInterface' . $interfaces, $code);
}
}

View File

@@ -0,0 +1,197 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\Method;
use Mockery\Generator\MockConfiguration;
use Mockery\Generator\Parameter;
use Mockery\Generator\TargetClassInterface;
use function array_filter;
use function array_merge;
use function end;
use function in_array;
use function is_array;
use function preg_match;
use function preg_match_all;
use function preg_replace;
use function rtrim;
use function sprintf;
class MagicMethodTypeHintsPass implements Pass
{
/**
* @var array
*/
private $mockMagicMethods = [
'__construct',
'__destruct',
'__call',
'__callStatic',
'__get',
'__set',
'__isset',
'__unset',
'__sleep',
'__wakeup',
'__toString',
'__invoke',
'__set_state',
'__clone',
'__debugInfo',
];
/**
* Apply implementation.
*
* @param string $code
*
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$magicMethods = $this->getMagicMethods($config->getTargetClass());
foreach ($config->getTargetInterfaces() as $interface) {
$magicMethods = array_merge($magicMethods, $this->getMagicMethods($interface));
}
foreach ($magicMethods as $method) {
$code = $this->applyMagicTypeHints($code, $method);
}
return $code;
}
/**
* Returns the magic methods within the
* passed DefinedTargetClass.
*
* @return array
*/
public function getMagicMethods(?TargetClassInterface $class = null)
{
if (! $class instanceof TargetClassInterface) {
return [];
}
return array_filter($class->getMethods(), function (Method $method) {
return in_array($method->getName(), $this->mockMagicMethods, true);
});
}
protected function renderTypeHint(Parameter $param)
{
$typeHint = $param->getTypeHint();
return $typeHint === null ? '' : sprintf('%s ', $typeHint);
}
/**
* Applies type hints of magic methods from
* class to the passed code.
*
* @param int $code
*
* @return string
*/
private function applyMagicTypeHints($code, Method $method)
{
if ($this->isMethodWithinCode($code, $method)) {
$namedParameters = $this->getOriginalParameters($code, $method);
$code = preg_replace(
$this->getDeclarationRegex($method->getName()),
$this->getMethodDeclaration($method, $namedParameters),
$code
);
}
return $code;
}
/**
* Returns a regex string used to match the
* declaration of some method.
*
* @param string $methodName
*
* @return string
*/
private function getDeclarationRegex($methodName)
{
return sprintf('/public\s+(?:static\s+)?function\s+%s\s*\(.*\)\s*(?=\{)/i', $methodName);
}
/**
* Gets the declaration code, as a string, for the passed method.
*
* @param array $namedParameters
*
* @return string
*/
private function getMethodDeclaration(Method $method, array $namedParameters)
{
$declaration = 'public';
$declaration .= $method->isStatic() ? ' static' : '';
$declaration .= ' function ' . $method->getName() . '(';
foreach ($method->getParameters() as $index => $parameter) {
$declaration .= $this->renderTypeHint($parameter);
$name = $namedParameters[$index] ?? $parameter->getName();
$declaration .= '$' . $name;
$declaration .= ',';
}
$declaration = rtrim($declaration, ',');
$declaration .= ') ';
$returnType = $method->getReturnType();
if ($returnType !== null) {
$declaration .= sprintf(': %s', $returnType);
}
return $declaration;
}
/**
* Returns the method original parameters, as they're
* described in the $code string.
*
* @param int $code
*
* @return array
*/
private function getOriginalParameters($code, Method $method)
{
$matches = [];
$parameterMatches = [];
preg_match($this->getDeclarationRegex($method->getName()), $code, $matches);
if ($matches !== []) {
preg_match_all('/(?<=\$)(\w+)+/i', $matches[0], $parameterMatches);
}
$groupMatches = end($parameterMatches);
return is_array($groupMatches) ? $groupMatches : [$groupMatches];
}
/**
* Checks if the method is declared within code.
*
* @param int $code
*
* @return bool
*/
private function isMethodWithinCode($code, Method $method)
{
return preg_match($this->getDeclarationRegex($method->getName()), $code) === 1;
}
}

View File

@@ -0,0 +1,199 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\Method;
use Mockery\Generator\MockConfiguration;
use Mockery\Generator\Parameter;
use function array_values;
use function count;
use function enum_exists;
use function get_class;
use function implode;
use function in_array;
use function is_object;
use function preg_match;
use function sprintf;
use function strpos;
use function strrpos;
use function strtolower;
use function substr;
use function var_export;
use const PHP_VERSION_ID;
class MethodDefinitionPass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
foreach ($config->getMethodsToMock() as $method) {
if ($method->isPublic()) {
$methodDef = 'public';
} elseif ($method->isProtected()) {
$methodDef = 'protected';
} else {
$methodDef = 'private';
}
if ($method->isStatic()) {
$methodDef .= ' static';
}
$methodDef .= ' function ';
$methodDef .= $method->returnsReference() ? ' & ' : '';
$methodDef .= $method->getName();
$methodDef .= $this->renderParams($method, $config);
$methodDef .= $this->renderReturnType($method);
$methodDef .= $this->renderMethodBody($method, $config);
$code = $this->appendToClass($code, $methodDef);
}
return $code;
}
protected function appendToClass($class, $code)
{
$lastBrace = strrpos($class, '}');
return substr($class, 0, $lastBrace) . $code . "\n }\n";
}
protected function renderParams(Method $method, $config)
{
$class = $method->getDeclaringClass();
if ($class->isInternal()) {
$overrides = $config->getParameterOverrides();
if (isset($overrides[strtolower($class->getName())][$method->getName()])) {
return '(' . implode(',', $overrides[strtolower($class->getName())][$method->getName()]) . ')';
}
}
$methodParams = [];
$params = $method->getParameters();
$isPhp81 = PHP_VERSION_ID >= 80100;
foreach ($params as $param) {
$paramDef = $this->renderTypeHint($param);
$paramDef .= $param->isPassedByReference() ? '&' : '';
$paramDef .= $param->isVariadic() ? '...' : '';
$paramDef .= '$' . $param->getName();
if (! $param->isVariadic()) {
if ($param->isDefaultValueAvailable() !== false) {
$defaultValue = $param->getDefaultValue();
if (is_object($defaultValue)) {
$prefix = get_class($defaultValue);
if ($isPhp81) {
if (enum_exists($prefix)) {
$prefix = var_export($defaultValue, true);
} elseif (
! $param->isDefaultValueConstant() &&
// "Parameter #1 [ <optional> F\Q\CN $a = new \F\Q\CN(param1, param2: 2) ]
preg_match(
'#<optional>\s.*?\s=\snew\s(.*?)\s]$#',
$param->__toString(),
$matches
) === 1
) {
$prefix = 'new ' . $matches[1];
}
}
} else {
$prefix = var_export($defaultValue, true);
}
$paramDef .= ' = ' . $prefix;
} elseif ($param->isOptional()) {
$paramDef .= ' = null';
}
}
$methodParams[] = $paramDef;
}
return '(' . implode(', ', $methodParams) . ')';
}
protected function renderReturnType(Method $method)
{
$type = $method->getReturnType();
return $type ? sprintf(': %s', $type) : '';
}
protected function renderTypeHint(Parameter $param)
{
$typeHint = $param->getTypeHint();
return $typeHint === null ? '' : sprintf('%s ', $typeHint);
}
private function renderMethodBody($method, $config)
{
$invoke = $method->isStatic() ? 'static::_mockery_handleStaticMethodCall' : '$this->_mockery_handleMethodCall';
$body = <<<BODY
{
\$argc = func_num_args();
\$argv = func_get_args();
BODY;
// Fix up known parameters by reference - used func_get_args() above
// in case more parameters are passed in than the function definition
// says - eg varargs.
$class = $method->getDeclaringClass();
$class_name = strtolower($class->getName());
$overrides = $config->getParameterOverrides();
if (isset($overrides[$class_name][$method->getName()])) {
$params = array_values($overrides[$class_name][$method->getName()]);
$paramCount = count($params);
for ($i = 0; $i < $paramCount; ++$i) {
$param = $params[$i];
if (strpos($param, '&') !== false) {
$body .= <<<BODY
if (\$argc > {$i}) {
\$argv[{$i}] = {$param};
}
BODY;
}
}
} else {
$params = array_values($method->getParameters());
$paramCount = count($params);
for ($i = 0; $i < $paramCount; ++$i) {
$param = $params[$i];
if (! $param->isPassedByReference()) {
continue;
}
$body .= <<<BODY
if (\$argc > {$i}) {
\$argv[{$i}] =& \${$param->getName()};
}
BODY;
}
}
$body .= "\$ret = {$invoke}(__FUNCTION__, \$argv);\n";
if (! in_array($method->getReturnType(), ['never', 'void'], true)) {
$body .= "return \$ret;\n";
}
return $body . "}\n";
}
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
interface Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config);
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use Mockery\Generator\TargetClassInterface;
use function preg_replace;
/**
* The standard Mockery\Mock class includes some methods to ease mocking, such
* as __wakeup, however if the target has a final __wakeup method, it can't be
* mocked. This pass removes the builtin methods where they are final on the
* target
*/
class RemoveBuiltinMethodsThatAreFinalPass implements Pass
{
protected $methods = [
'__wakeup' => '/public function __wakeup\(\)\s+\{.*?\}/sm',
'__toString' => '/public function __toString\(\)\s+(:\s+string)?\s*\{.*?\}/sm',
];
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$target = $config->getTargetClass();
if (! $target instanceof TargetClassInterface) {
return $code;
}
foreach ($target->getMethods() as $method) {
if (! $method->isFinal()) {
continue;
}
if (! isset($this->methods[$method->getName()])) {
continue;
}
$code = preg_replace($this->methods[$method->getName()], '', $code);
}
return $code;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use function preg_replace;
/**
* Remove mock's empty destructor if we tend to use original class destructor
*/
class RemoveDestructorPass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$target = $config->getTargetClass();
if (! $target) {
return $code;
}
if (! $config->isMockOriginalDestructor()) {
return preg_replace('/public function __destruct\(\)\s+\{.*?\}/sm', '', $code);
}
return $code;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use function strrpos;
use function substr;
use const PHP_VERSION_ID;
/**
* Internal classes can not be instantiated with the newInstanceWithoutArgs
* reflection method, so need the serialization hack. If the class also
* implements Serializable, we need to replace the standard unserialize method
* definition with a dummy
*/
class RemoveUnserializeForInternalSerializableClassesPass implements Pass
{
public const DUMMY_METHOD_DEFINITION = 'public function unserialize(string $data): void {} ';
public const DUMMY_METHOD_DEFINITION_LEGACY = 'public function unserialize($string) {} ';
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$target = $config->getTargetClass();
if (! $target) {
return $code;
}
if (! $target->hasInternalAncestor() || ! $target->implementsInterface('Serializable')) {
return $code;
}
return $this->appendToClass(
$code,
PHP_VERSION_ID < 80100 ? self::DUMMY_METHOD_DEFINITION_LEGACY : self::DUMMY_METHOD_DEFINITION
);
}
protected function appendToClass($class, $code)
{
$lastBrace = strrpos($class, '}');
return substr($class, 0, $lastBrace) . $code . "\n }\n";
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator\StringManipulation\Pass;
use Mockery\Generator\MockConfiguration;
use function array_map;
use function implode;
use function ltrim;
use function preg_replace;
class TraitPass implements Pass
{
/**
* @param string $code
* @return string
*/
public function apply($code, MockConfiguration $config)
{
$traits = $config->getTargetTraits();
if ($traits === []) {
return $code;
}
$useStatements = array_map(static function ($trait) {
return 'use \\\\' . ltrim($trait->getName(), '\\') . ';';
}, $traits);
return preg_replace('/^{$/m', "{\n " . implode("\n ", $useStatements) . "\n", $code);
}
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
use Mockery\Generator\StringManipulation\Pass\AvoidMethodClashPass;
use Mockery\Generator\StringManipulation\Pass\CallTypeHintPass;
use Mockery\Generator\StringManipulation\Pass\ClassAttributesPass;
use Mockery\Generator\StringManipulation\Pass\ClassNamePass;
use Mockery\Generator\StringManipulation\Pass\ClassPass;
use Mockery\Generator\StringManipulation\Pass\ConstantsPass;
use Mockery\Generator\StringManipulation\Pass\InstanceMockPass;
use Mockery\Generator\StringManipulation\Pass\InterfacePass;
use Mockery\Generator\StringManipulation\Pass\MagicMethodTypeHintsPass;
use Mockery\Generator\StringManipulation\Pass\MethodDefinitionPass;
use Mockery\Generator\StringManipulation\Pass\Pass;
use Mockery\Generator\StringManipulation\Pass\RemoveBuiltinMethodsThatAreFinalPass;
use Mockery\Generator\StringManipulation\Pass\RemoveDestructorPass;
use Mockery\Generator\StringManipulation\Pass\RemoveUnserializeForInternalSerializableClassesPass;
use Mockery\Generator\StringManipulation\Pass\TraitPass;
use function file_get_contents;
class StringManipulationGenerator implements Generator
{
/**
* @var list<Pass>
*/
protected $passes = [];
/**
* @var string
*/
private $code;
/**
* @param list<Pass> $passes
*/
public function __construct(array $passes)
{
$this->passes = $passes;
$this->code = file_get_contents(__DIR__ . '/../Mock.php');
}
/**
* @param Pass $pass
* @return void
*/
public function addPass(Pass $pass)
{
$this->passes[] = $pass;
}
/**
* @return MockDefinition
*/
public function generate(MockConfiguration $config)
{
$className = $config->getName() ?: $config->generateName();
$namedConfig = $config->rename($className);
$code = $this->code;
foreach ($this->passes as $pass) {
$code = $pass->apply($code, $namedConfig);
}
return new MockDefinition($namedConfig, $code);
}
/**
* Creates a new StringManipulationGenerator with the default passes
*
* @return StringManipulationGenerator
*/
public static function withDefaultPasses()
{
return new static([
new CallTypeHintPass(),
new MagicMethodTypeHintsPass(),
new ClassPass(),
new TraitPass(),
new ClassNamePass(),
new InstanceMockPass(),
new InterfacePass(),
new AvoidMethodClashPass(),
new MethodDefinitionPass(),
new RemoveUnserializeForInternalSerializableClassesPass(),
new RemoveBuiltinMethodsThatAreFinalPass(),
new RemoveDestructorPass(),
new ConstantsPass(),
new ClassAttributesPass(),
]);
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
interface TargetClassInterface
{
/**
* Returns a new instance of the current TargetClassInterface's implementation.
*
* @param class-string $name
*
* @return TargetClassInterface
*/
public static function factory($name);
/**
* Returns the targetClass's attributes.
*
* @return array<class-string>
*/
public function getAttributes();
/**
* Returns the targetClass's interfaces.
*
* @return array<TargetClassInterface>
*/
public function getInterfaces();
/**
* Returns the targetClass's methods.
*
* @return array<Method>
*/
public function getMethods();
/**
* Returns the targetClass's name.
*
* @return class-string
*/
public function getName();
/**
* Returns the targetClass's namespace name.
*
* @return string
*/
public function getNamespaceName();
/**
* Returns the targetClass's short name.
*
* @return string
*/
public function getShortName();
/**
* Returns whether the targetClass has
* an internal ancestor.
*
* @return bool
*/
public function hasInternalAncestor();
/**
* Returns whether the targetClass is in
* the passed interface.
*
* @param class-string|string $interface
*
* @return bool
*/
public function implementsInterface($interface);
/**
* Returns whether the targetClass is in namespace.
*
* @return bool
*/
public function inNamespace();
/**
* Returns whether the targetClass is abstract.
*
* @return bool
*/
public function isAbstract();
/**
* Returns whether the targetClass is final.
*
* @return bool
*/
public function isFinal();
}

View File

@@ -0,0 +1,141 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Generator;
use function array_pop;
use function explode;
use function implode;
use function ltrim;
class UndefinedTargetClass implements TargetClassInterface
{
/**
* @var class-string
*/
private $name;
/**
* @param class-string $name
*/
public function __construct($name)
{
$this->name = $name;
}
/**
* @return class-string
*/
public function __toString()
{
return $this->name;
}
/**
* @param class-string $name
* @return self
*/
public static function factory($name)
{
return new self($name);
}
/**
* @return list<class-string>
*/
public function getAttributes()
{
return [];
}
/**
* @return list<self>
*/
public function getInterfaces()
{
return [];
}
/**
* @return list<Method>
*/
public function getMethods()
{
return [];
}
/**
* @return class-string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getNamespaceName()
{
$parts = explode('\\', ltrim($this->getName(), '\\'));
array_pop($parts);
return implode('\\', $parts);
}
/**
* @return string
*/
public function getShortName()
{
$parts = explode('\\', $this->getName());
return array_pop($parts);
}
/**
* @return bool
*/
public function hasInternalAncestor()
{
return false;
}
/**
* @param class-string $interface
* @return bool
*/
public function implementsInterface($interface)
{
return false;
}
/**
* @return bool
*/
public function inNamespace()
{
return $this->getNamespaceName() !== '';
}
/**
* @return bool
*/
public function isAbstract()
{
return false;
}
/**
* @return bool
*/
public function isFinal()
{
return false;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use Closure;
/**
* @method Expectation withArgs(array|Closure $args)
*/
class HigherOrderMessage
{
/**
* @var string
*/
private $method;
/**
* @var LegacyMockInterface|MockInterface
*/
private $mock;
public function __construct(MockInterface $mock, $method)
{
$this->mock = $mock;
$this->method = $method;
}
/**
* @param string $method
* @param array $args
*
* @return Expectation|ExpectationInterface|HigherOrderMessage
*/
public function __call($method, $args)
{
if ($this->method === 'shouldNotHaveReceived') {
return $this->mock->{$this->method}($method, $args);
}
$expectation = $this->mock->{$this->method}($method);
return $expectation->withArgs($args);
}
}

View File

@@ -0,0 +1,147 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use Closure;
use Exception;
use InvalidArgumentException;
use ReflectionClass;
use UnexpectedValueException;
use function class_exists;
use function restore_error_handler;
use function set_error_handler;
use function sprintf;
use function strlen;
use function unserialize;
/**
* This is a trimmed down version of https://github.com/doctrine/instantiator, without the caching mechanism.
*/
final class Instantiator
{
/**
* @template TClass of object
*
* @param class-string<TClass> $className
*
* @throws InvalidArgumentException
* @throws UnexpectedValueException
*
* @return TClass
*/
public function instantiate($className): object
{
return $this->buildFactory($className)();
}
/**
* @throws UnexpectedValueException
*/
private function attemptInstantiationViaUnSerialization(
ReflectionClass $reflectionClass,
string $serializedString
): void {
set_error_handler(static function ($code, $message, $file, $line) use ($reflectionClass, &$error): void {
$msg = sprintf(
'Could not produce an instance of "%s" via un-serialization, since an error was triggered in file "%s" at line "%d"',
$reflectionClass->getName(),
$file,
$line
);
$error = new UnexpectedValueException($msg, 0, new Exception($message, $code));
});
try {
unserialize($serializedString);
} catch (Exception $exception) {
restore_error_handler();
throw new UnexpectedValueException(
sprintf(
'An exception was raised while trying to instantiate an instance of "%s" via un-serialization',
$reflectionClass->getName()
),
0,
$exception
);
}
restore_error_handler();
if ($error instanceof UnexpectedValueException) {
throw $error;
}
}
/**
* Builds a {@see Closure} capable of instantiating the given $className without invoking its constructor.
*/
private function buildFactory(string $className): Closure
{
$reflectionClass = $this->getReflectionClass($className);
if ($this->isInstantiableViaReflection($reflectionClass)) {
return static function () use ($reflectionClass) {
return $reflectionClass->newInstanceWithoutConstructor();
};
}
$serializedString = sprintf('O:%d:"%s":0:{}', strlen($className), $className);
$this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
return static function () use ($serializedString) {
return unserialize($serializedString);
};
}
/**
* @throws InvalidArgumentException
*/
private function getReflectionClass(string $className): ReflectionClass
{
if (! class_exists($className)) {
throw new InvalidArgumentException(sprintf('Class:%s does not exist', $className));
}
$reflection = new ReflectionClass($className);
if ($reflection->isAbstract()) {
throw new InvalidArgumentException(sprintf('Class:%s is an abstract class', $className));
}
return $reflection;
}
/**
* Verifies whether the given class is to be considered internal
*/
private function hasInternalAncestors(ReflectionClass $reflectionClass): bool
{
do {
if ($reflectionClass->isInternal()) {
return true;
}
} while ($reflectionClass = $reflectionClass->getParentClass());
return false;
}
/**
* Verifies if the class is instantiable via reflection
*/
private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool
{
return ! ($reflectionClass->isInternal() && $reflectionClass->isFinal());
}
}

View File

@@ -0,0 +1,258 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use Closure;
use Throwable;
interface LegacyMockInterface
{
/**
* In the event shouldReceive() accepting an array of methods/returns
* this method will switch them from normal expectations to default
* expectations
*
* @return self
*/
public function byDefault();
/**
* Set mock to defer unexpected methods to its parent if possible
*
* @return self
*/
public function makePartial();
/**
* Fetch the next available allocation order number
*
* @return int
*/
public function mockery_allocateOrder();
/**
* Find an expectation matching the given method and arguments
*
* @template TMixed
*
* @param string $method
* @param array<TMixed> $args
*
* @return null|Expectation
*/
public function mockery_findExpectation($method, array $args);
/**
* Return the container for this mock
*
* @return Container
*/
public function mockery_getContainer();
/**
* Get current ordered number
*
* @return int
*/
public function mockery_getCurrentOrder();
/**
* Gets the count of expectations for this mock
*
* @return int
*/
public function mockery_getExpectationCount();
/**
* Return the expectations director for the given method
*
* @param string $method
*
* @return null|ExpectationDirector
*/
public function mockery_getExpectationsFor($method);
/**
* Fetch array of ordered groups
*
* @return array<string,int>
*/
public function mockery_getGroups();
/**
* @return string[]
*/
public function mockery_getMockableMethods();
/**
* @return array
*/
public function mockery_getMockableProperties();
/**
* Return the name for this mock
*
* @return string
*/
public function mockery_getName();
/**
* Alternative setup method to constructor
*
* @param object $partialObject
*
* @return void
*/
public function mockery_init(?Container $container = null, $partialObject = null);
/**
* @return bool
*/
public function mockery_isAnonymous();
/**
* Set current ordered number
*
* @param int $order
*
* @return int
*/
public function mockery_setCurrentOrder($order);
/**
* Return the expectations director for the given method
*
* @param string $method
*
* @return null|ExpectationDirector
*/
public function mockery_setExpectationsFor($method, ExpectationDirector $director);
/**
* Set ordering for a group
*
* @param string $group
* @param int $order
*
* @return void
*/
public function mockery_setGroup($group, $order);
/**
* Tear down tasks for this mock
*
* @return void
*/
public function mockery_teardown();
/**
* Validate the current mock's ordering
*
* @param string $method
* @param int $order
*
* @throws Exception
*
* @return void
*/
public function mockery_validateOrder($method, $order);
/**
* Iterate across all expectation directors and validate each
*
* @throws Throwable
*
* @return void
*/
public function mockery_verify();
/**
* Allows additional methods to be mocked that do not explicitly exist on mocked class
*
* @param string $method the method name to be mocked
* @return self
*/
public function shouldAllowMockingMethod($method);
/**
* @return self
*/
public function shouldAllowMockingProtectedMethods();
/**
* Set mock to defer unexpected methods to its parent if possible
*
* @deprecated since 1.4.0. Please use makePartial() instead.
*
* @return self
*/
public function shouldDeferMissing();
/**
* @return self
*/
public function shouldHaveBeenCalled();
/**
* @template TMixed
* @param string $method
* @param null|array<TMixed>|Closure $args
*
* @return self
*/
public function shouldHaveReceived($method, $args = null);
/**
* Set mock to ignore unexpected methods and return Undefined class
*
* @template TReturnValue
*
* @param null|TReturnValue $returnValue the default return value for calls to missing functions on this mock
*
* @return self
*/
public function shouldIgnoreMissing($returnValue = null);
/**
* @template TMixed
* @param null|array<TMixed> $args (optional)
*
* @return self
*/
public function shouldNotHaveBeenCalled(?array $args = null);
/**
* @template TMixed
* @param string $method
* @param null|array<TMixed>|Closure $args
*
* @return self
*/
public function shouldNotHaveReceived($method, $args = null);
/**
* Shortcut method for setting an expectation that a method should not be called.
*
* @param string ...$methodNames one or many methods that are expected not to be called in this mock
*
* @return Expectation|ExpectationInterface|HigherOrderMessage
*/
public function shouldNotReceive(...$methodNames);
/**
* Set expected method calls
*
* @param string ...$methodNames one or many methods that are expected to be called in this mock
*
* @return Expectation|ExpectationInterface|HigherOrderMessage
*/
public function shouldReceive(...$methodNames);
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Loader;
use Mockery\Generator\MockDefinition;
use function class_exists;
class EvalLoader implements Loader
{
/**
* Load the given mock definition
*
* @return void
*/
public function load(MockDefinition $definition)
{
if (class_exists($definition->getClassName(), false)) {
return;
}
eval('?>' . $definition->getCode());
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Loader;
use Mockery\Generator\MockDefinition;
interface Loader
{
/**
* Load the given mock definition
*
* @return void
*/
public function load(MockDefinition $definition);
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Loader;
use Mockery\Generator\MockDefinition;
use function array_diff;
use function class_exists;
use function file_exists;
use function file_put_contents;
use function glob;
use function realpath;
use function sprintf;
use function sys_get_temp_dir;
use function uniqid;
use function unlink;
use const DIRECTORY_SEPARATOR;
class RequireLoader implements Loader
{
/**
* @var string
*/
protected $lastPath = '';
/**
* @var string
*/
protected $path;
/**
* @param string|null $path
*/
public function __construct($path = null)
{
if ($path === null) {
$path = sys_get_temp_dir();
}
$this->path = realpath($path);
}
public function __destruct()
{
$files = array_diff(glob($this->path . DIRECTORY_SEPARATOR . 'Mockery_*.php') ?: [], [$this->lastPath]);
foreach ($files as $file) {
@unlink($file);
}
}
/**
* Load the given mock definition
*
* @return void
*/
public function load(MockDefinition $definition)
{
if (class_exists($definition->getClassName(), false)) {
return;
}
$this->lastPath = sprintf('%s%s%s.php', $this->path, DIRECTORY_SEPARATOR, uniqid('Mockery_', false));
file_put_contents($this->lastPath, $definition->getCode());
if (file_exists($this->lastPath)) {
require $this->lastPath;
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
class AndAnyOtherArgs extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<AndAnyOthers>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return true;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
class Any extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<Any>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return true;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
class AnyArgs extends MatcherAbstract implements ArgumentListMatcher
{
public function __toString()
{
return '<Any Arguments>';
}
/**
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return true;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use function in_array;
class AnyOf extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<AnyOf>';
}
/**
* Check if the actual value does not match the expected (in this
* case it's specifically NOT expected).
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return in_array($actual, $this->_expected, true);
}
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
interface ArgumentListMatcher
{
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
class Closure extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<Closure===true>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return ($this->_expected)($actual) === true;
}
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use function array_values;
use function implode;
class Contains extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
$elements = [];
foreach ($this->_expected as $v) {
$elements[] = (string) $v;
}
return '<Contains[' . implode(', ', $elements) . ']>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
$values = array_values($actual);
foreach ($this->_expected as $exp) {
$match = false;
foreach ($values as $val) {
if ($exp === $val || $exp == $val) {
$match = true;
break;
}
}
if ($match === false) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use function implode;
use function is_object;
use function method_exists;
class Ducktype extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<Ducktype[' . implode(', ', $this->_expected) . ']>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
if (! is_object($actual)) {
return false;
}
foreach ($this->_expected as $method) {
if (! method_exists($actual, $method)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use ArrayAccess;
use function array_key_exists;
use function is_array;
use function sprintf;
class HasKey extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return sprintf('<HasKey[%s]>', $this->_expected);
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
if (! is_array($actual) && ! $actual instanceof ArrayAccess) {
return false;
}
return array_key_exists($this->_expected, (array) $actual);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use ArrayAccess;
use function in_array;
use function is_array;
class HasValue extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<HasValue[' . (string) $this->_expected . ']>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
if (! is_array($actual) && ! $actual instanceof ArrayAccess) {
return false;
}
return in_array($this->_expected, (array) $actual, true);
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
class IsEqual extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<IsEqual>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return $this->_expected == $actual;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
class IsSame extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<IsSame>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return $this->_expected === $actual;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
/**
* @deprecated Implement \Mockery\Matcher\MatcherInterface instead of extending this class
* @see https://github.com/mockery/mockery/pull/1338
*/
abstract class MatcherAbstract implements MatcherInterface
{
/**
* The expected value (or part thereof)
*
* @template TExpected
*
* @var TExpected
*/
protected $_expected = null;
/**
* Set the expected value
*
* @template TExpected
*
* @param TExpected $expected
*/
public function __construct($expected = null)
{
$this->_expected = $expected;
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
interface MatcherInterface
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString();
/**
* Check if the actual value matches the expected.
* Actual passed by reference to preserve reference trail (where applicable)
* back to the original method parameter.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual);
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
class MultiArgumentClosure extends MatcherAbstract implements ArgumentListMatcher
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<MultiArgumentClosure===true>';
}
/**
* Check if the actual value matches the expected.
* Actual passed by reference to preserve reference trail (where applicable)
* back to the original method parameter.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return ($this->_expected)(...$actual) === true;
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use function is_object;
/**
* @deprecated 2.0 Due to ambiguity, use PHPUnit equivalents
*/
class MustBe extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<MustBe>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
if (! is_object($actual)) {
return $this->_expected === $actual;
}
return $this->_expected == $actual;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use function count;
class NoArgs extends MatcherAbstract implements ArgumentListMatcher
{
public function __toString()
{
return '<No Arguments>';
}
/**
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return count($actual) === 0;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
class Not extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<Not>';
}
/**
* Check if the actual value does not match the expected (in this
* case it's specifically NOT expected).
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return $actual !== $this->_expected;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
class NotAnyOf extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<AnyOf>';
}
/**
* Check if the actual value does not match the expected (in this
* case it's specifically NOT expected).
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
foreach ($this->_expected as $exp) {
if ($actual === $exp || $actual == $exp) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use function preg_match;
class Pattern extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<Pattern>';
}
/**
* Check if the actual value matches the expected pattern.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
return preg_match($this->_expected, (string) $actual) >= 1;
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use function array_replace_recursive;
use function implode;
use function is_array;
class Subset extends MatcherAbstract
{
private $expected;
private $strict = true;
/**
* @param array $expected Expected subset of data
* @param bool $strict Whether to run a strict or loose comparison
*/
public function __construct(array $expected, $strict = true)
{
$this->expected = $expected;
$this->strict = $strict;
}
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<Subset' . $this->formatArray($this->expected) . '>';
}
/**
* @param array $expected Expected subset of data
*
* @return Subset
*/
public static function loose(array $expected)
{
return new static($expected, false);
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
if (! is_array($actual)) {
return false;
}
if ($this->strict) {
return $actual === array_replace_recursive($actual, $this->expected);
}
return $actual == array_replace_recursive($actual, $this->expected);
}
/**
* @param array $expected Expected subset of data
*
* @return Subset
*/
public static function strict(array $expected)
{
return new static($expected, true);
}
/**
* Recursively format an array into the string representation for this matcher
*
* @return string
*/
protected function formatArray(array $array)
{
$elements = [];
foreach ($array as $k => $v) {
$elements[] = $k . '=' . (is_array($v) ? $this->formatArray($v) : (string) $v);
}
return '[' . implode(', ', $elements) . ']';
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery\Matcher;
use function class_exists;
use function function_exists;
use function interface_exists;
use function is_string;
use function strtolower;
use function ucfirst;
class Type extends MatcherAbstract
{
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return '<' . ucfirst($this->_expected) . '>';
}
/**
* Check if the actual value matches the expected.
*
* @template TMixed
*
* @param TMixed $actual
*
* @return bool
*/
public function match(&$actual)
{
$function = $this->_expected === 'real' ? 'is_float' : 'is_' . strtolower($this->_expected);
if (function_exists($function)) {
return $function($actual);
}
if (! is_string($this->_expected)) {
return false;
}
if (class_exists($this->_expected) || interface_exists($this->_expected)) {
return $actual instanceof $this->_expected;
}
return false;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
class MethodCall
{
/**
* @var array
*/
private $args;
/**
* @var string
*/
private $method;
/**
* @param string $method
* @param array $args
*/
public function __construct($method, $args)
{
$this->method = $method;
$this->args = $args;
}
/**
* @return array
*/
public function getArgs()
{
return $this->args;
}
/**
* @return string
*/
public function getMethod()
{
return $this->method;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
interface MockInterface extends LegacyMockInterface
{
/**
* @param mixed $something String method name or map of method => return
*
* @return Expectation|ExpectationInterface|HigherOrderMessage|self
*/
public function allows($something = []);
/**
* @param mixed $something String method name (optional)
*
* @return Expectation|ExpectationInterface|ExpectsHigherOrderMessage
*/
public function expects($something = null);
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
class QuickDefinitionsConfiguration
{
private const QUICK_DEFINITIONS_MODE_DEFAULT_EXPECTATION = 'QUICK_DEFINITIONS_MODE_DEFAULT_EXPECTATION';
private const QUICK_DEFINITIONS_MODE_MOCK_AT_LEAST_ONCE = 'QUICK_DEFINITIONS_MODE_MOCK_AT_LEAST_ONCE';
/**
* Defines what a quick definition should produce.
* Possible options are:
* - self::QUICK_DEFINITIONS_MODE_DEFAULT_EXPECTATION: in this case quick
* definitions define a stub.
* - self::QUICK_DEFINITIONS_MODE_MOCK_AT_LEAST_ONCE: in this case quick
* definitions define a mock with an 'at least once' expectation.
*
* @var string
*/
protected $_quickDefinitionsApplicationMode = self::QUICK_DEFINITIONS_MODE_DEFAULT_EXPECTATION;
/**
* Returns true if quick definitions should setup a stub, returns false when
* quick definitions should setup a mock with 'at least once' expectation.
* When parameter $newValue is specified it sets the configuration with the
* given value.
*/
public function shouldBeCalledAtLeastOnce(?bool $newValue = null): bool
{
if ($newValue !== null) {
$this->_quickDefinitionsApplicationMode = $newValue
? self::QUICK_DEFINITIONS_MODE_MOCK_AT_LEAST_ONCE
: self::QUICK_DEFINITIONS_MODE_DEFAULT_EXPECTATION;
}
return $this->_quickDefinitionsApplicationMode === self::QUICK_DEFINITIONS_MODE_MOCK_AT_LEAST_ONCE;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
class ReceivedMethodCalls
{
private $methodCalls = [];
public function push(MethodCall $methodCall)
{
$this->methodCalls[] = $methodCall;
}
public function verify(Expectation $expectation)
{
foreach ($this->methodCalls as $methodCall) {
if ($methodCall->getMethod() !== $expectation->getName()) {
continue;
}
if (! $expectation->matchArgs($methodCall->getArgs())) {
continue;
}
$expectation->verifyCall($methodCall->getArgs());
}
$expectation->verify();
}
}

View File

@@ -0,0 +1,316 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionIntersectionType;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionType;
use ReflectionUnionType;
use function array_diff;
use function array_intersect;
use function array_map;
use function array_merge;
use function get_debug_type;
use function implode;
use function in_array;
use function method_exists;
use function sprintf;
use function strpos;
use const PHP_VERSION_ID;
/**
* @internal
*/
class Reflector
{
/**
* List of built-in types.
*
* @var list<string>
*/
public const BUILTIN_TYPES = ['array', 'bool', 'int', 'float', 'null', 'object', 'string'];
/**
* List of reserved words.
*
* @var list<string>
*/
public const RESERVED_WORDS = ['bool', 'true', 'false', 'float', 'int', 'iterable', 'mixed', 'never', 'null', 'object', 'string', 'void'];
/**
* Iterable.
*
* @var list<string>
*/
private const ITERABLE = ['iterable'];
/**
* Traversable array.
*
* @var list<string>
*/
private const TRAVERSABLE_ARRAY = ['\Traversable', 'array'];
/**
* Compute the string representation for the return type.
*
* @param bool $withoutNullable
*
* @return null|string
*/
public static function getReturnType(ReflectionMethod $method, $withoutNullable = false)
{
$type = $method->getReturnType();
if (! $type instanceof ReflectionType && method_exists($method, 'getTentativeReturnType')) {
$type = $method->getTentativeReturnType();
}
if (! $type instanceof ReflectionType) {
return null;
}
$typeHint = self::getTypeFromReflectionType($type, $method->getDeclaringClass());
return (! $withoutNullable && $type->allowsNull()) ? self::formatNullableType($typeHint) : $typeHint;
}
/**
* Compute the string representation for the simplest return type.
*
* @return null|string
*/
public static function getSimplestReturnType(ReflectionMethod $method)
{
$type = $method->getReturnType();
if (! $type instanceof ReflectionType && method_exists($method, 'getTentativeReturnType')) {
$type = $method->getTentativeReturnType();
}
if (! $type instanceof ReflectionType || $type->allowsNull()) {
return null;
}
$typeInformation = self::getTypeInformation($type, $method->getDeclaringClass());
// return the first primitive type hint
foreach ($typeInformation as $info) {
if ($info['isPrimitive']) {
return $info['typeHint'];
}
}
// if no primitive type, return the first type
foreach ($typeInformation as $info) {
return $info['typeHint'];
}
return null;
}
/**
* Compute the string representation for the paramater type.
*
* @param bool $withoutNullable
*
* @return null|string
*/
public static function getTypeHint(ReflectionParameter $param, $withoutNullable = false)
{
if (! $param->hasType()) {
return null;
}
$type = $param->getType();
$declaringClass = $param->getDeclaringClass();
$typeHint = self::getTypeFromReflectionType($type, $declaringClass);
return (! $withoutNullable && $type->allowsNull()) ? self::formatNullableType($typeHint) : $typeHint;
}
/**
* Determine if the parameter is typed as an array.
*
* @return bool
*/
public static function isArray(ReflectionParameter $param)
{
$type = $param->getType();
return $type instanceof ReflectionNamedType && $type->getName();
}
/**
* Determine if the given type is a reserved word.
*/
public static function isReservedWord(string $type): bool
{
return in_array(strtolower($type), self::RESERVED_WORDS, true);
}
/**
* Format the given type as a nullable type.
*/
private static function formatNullableType(string $typeHint): string
{
if ($typeHint === 'mixed') {
return $typeHint;
}
if (strpos($typeHint, 'null') !== false) {
return $typeHint;
}
if (PHP_VERSION_ID < 80000) {
return sprintf('?%s', $typeHint);
}
return sprintf('%s|null', $typeHint);
}
private static function getTypeFromReflectionType(ReflectionType $type, ReflectionClass $declaringClass): string
{
if ($type instanceof ReflectionNamedType) {
$typeHint = $type->getName();
if ($type->isBuiltin()) {
return $typeHint;
}
if ($typeHint === 'static') {
return $typeHint;
}
// 'self' needs to be resolved to the name of the declaring class
if ($typeHint === 'self') {
$typeHint = $declaringClass->getName();
}
// 'parent' needs to be resolved to the name of the parent class
if ($typeHint === 'parent') {
$typeHint = $declaringClass->getParentClass()->getName();
}
// class names need prefixing with a slash
return sprintf('\\%s', $typeHint);
}
if ($type instanceof ReflectionIntersectionType) {
$types = array_map(
static function (ReflectionType $type) use ($declaringClass): string {
return self::getTypeFromReflectionType($type, $declaringClass);
},
$type->getTypes()
);
return implode('&', $types);
}
if ($type instanceof ReflectionUnionType) {
$types = array_map(
static function (ReflectionType $type) use ($declaringClass): string {
return self::getTypeFromReflectionType($type, $declaringClass);
},
$type->getTypes()
);
$intersect = array_intersect(self::TRAVERSABLE_ARRAY, $types);
if ($intersect === self::TRAVERSABLE_ARRAY) {
$types = array_merge(self::ITERABLE, array_diff($types, self::TRAVERSABLE_ARRAY));
}
return implode(
'|',
array_map(
static function (string $type): string {
return strpos($type, '&') === false ? $type : sprintf('(%s)', $type);
},
$types
)
);
}
throw new InvalidArgumentException('Unknown ReflectionType: ' . get_debug_type($type));
}
/**
* Get the string representation of the given type.
*
* @return list<array{typeHint:string,isPrimitive:bool}>
*/
private static function getTypeInformation(ReflectionType $type, ReflectionClass $declaringClass): array
{
// PHP 8 union types and PHP 8.1 intersection types can be recursively processed
if ($type instanceof ReflectionUnionType || $type instanceof ReflectionIntersectionType) {
$types = [];
foreach ($type->getTypes() as $innterType) {
foreach (self::getTypeInformation($innterType, $declaringClass) as $info) {
if ($info['typeHint'] === 'null' && $info['isPrimitive']) {
continue;
}
$types[] = $info;
}
}
return $types;
}
// $type must be an instance of \ReflectionNamedType
$typeHint = $type->getName();
// builtins can be returned as is
if ($type->isBuiltin()) {
return [
[
'typeHint' => $typeHint,
'isPrimitive' => in_array($typeHint, self::BUILTIN_TYPES, true),
],
];
}
// 'static' can be returned as is
if ($typeHint === 'static') {
return [
[
'typeHint' => $typeHint,
'isPrimitive' => false,
],
];
}
// 'self' needs to be resolved to the name of the declaring class
if ($typeHint === 'self') {
$typeHint = $declaringClass->getName();
}
// 'parent' needs to be resolved to the name of the parent class
if ($typeHint === 'parent') {
$typeHint = $declaringClass->getParentClass()->getName();
}
// class names need prefixing with a slash
return [
[
'typeHint' => sprintf('\\%s', $typeHint),
'isPrimitive' => false,
],
];
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
use function spl_object_hash;
class Undefined
{
/**
* Call capturing to merely return this same object.
*
* @param string $method
* @param array $args
*
* @return self
*/
public function __call($method, array $args)
{
return $this;
}
/**
* Return a string, avoiding E_RECOVERABLE_ERROR.
*
* @return string
*/
public function __toString()
{
return self::class . ':' . spl_object_hash($this);
}
}

View File

@@ -0,0 +1,168 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
class VerificationDirector
{
/**
* @var VerificationExpectation
*/
private $expectation;
/**
* @var ReceivedMethodCalls
*/
private $receivedMethodCalls;
public function __construct(ReceivedMethodCalls $receivedMethodCalls, VerificationExpectation $expectation)
{
$this->receivedMethodCalls = $receivedMethodCalls;
$this->expectation = $expectation;
}
/**
* @return self
*/
public function atLeast()
{
return $this->cloneWithoutCountValidatorsApplyAndVerify('atLeast', []);
}
/**
* @return self
*/
public function atMost()
{
return $this->cloneWithoutCountValidatorsApplyAndVerify('atMost', []);
}
/**
* @param int $minimum
* @param int $maximum
*
* @return self
*/
public function between($minimum, $maximum)
{
return $this->cloneWithoutCountValidatorsApplyAndVerify('between', [$minimum, $maximum]);
}
/**
* @return self
*/
public function once()
{
return $this->cloneWithoutCountValidatorsApplyAndVerify('once', []);
}
/**
* @param int $limit
*
* @return self
*/
public function times($limit = null)
{
return $this->cloneWithoutCountValidatorsApplyAndVerify('times', [$limit]);
}
/**
* @return self
*/
public function twice()
{
return $this->cloneWithoutCountValidatorsApplyAndVerify('twice', []);
}
public function verify()
{
$this->receivedMethodCalls->verify($this->expectation);
}
/**
* @template TArgs
*
* @param TArgs $args
*
* @return self
*/
public function with(...$args)
{
return $this->cloneApplyAndVerify('with', $args);
}
/**
* @return self
*/
public function withAnyArgs()
{
return $this->cloneApplyAndVerify('withAnyArgs', []);
}
/**
* @template TArgs
*
* @param TArgs $args
*
* @return self
*/
public function withArgs($args)
{
return $this->cloneApplyAndVerify('withArgs', [$args]);
}
/**
* @return self
*/
public function withNoArgs()
{
return $this->cloneApplyAndVerify('withNoArgs', []);
}
/**
* @param string $method
* @param array $args
*
* @return self
*/
protected function cloneApplyAndVerify($method, $args)
{
$verificationExpectation = clone $this->expectation;
$verificationExpectation->{$method}(...$args);
$verificationDirector = new self($this->receivedMethodCalls, $verificationExpectation);
$verificationDirector->verify();
return $verificationDirector;
}
/**
* @param string $method
* @param array $args
*
* @return self
*/
protected function cloneWithoutCountValidatorsApplyAndVerify($method, $args)
{
$verificationExpectation = clone $this->expectation;
$verificationExpectation->clearCountValidators();
$verificationExpectation->{$method}(...$args);
$verificationDirector = new self($this->receivedMethodCalls, $verificationExpectation);
$verificationDirector->verify();
return $verificationDirector;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
namespace Mockery;
class VerificationExpectation extends Expectation
{
public function __clone()
{
parent::__clone();
$this->_actualCount = 0;
}
/**
* @return void
*/
public function clearCountValidators()
{
$this->_countValidators = [];
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* Mockery (https://docs.mockery.io/)
*
* @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md
* @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License
* @link https://github.com/mockery/mockery for the canonical source repository
*/
use Mockery\LegacyMockInterface;
use Mockery\Matcher\AndAnyOtherArgs;
use Mockery\Matcher\AnyArgs;
use Mockery\MockInterface;
if (! \function_exists('mock')) {
/**
* @template TMock of object
*
* @param array<class-string<TMock>|TMock|Closure(LegacyMockInterface&MockInterface&TMock):LegacyMockInterface&MockInterface&TMock|array<TMock>> $args
*
* @return LegacyMockInterface&MockInterface&TMock
*/
function mock(...$args)
{
return Mockery::mock(...$args);
}
}
if (! \function_exists('spy')) {
/**
* @template TSpy of object
*
* @param array<class-string<TSpy>|TSpy|Closure(LegacyMockInterface&MockInterface&TSpy):LegacyMockInterface&MockInterface&TSpy|array<TSpy>> $args
*
* @return LegacyMockInterface&MockInterface&TSpy
*/
function spy(...$args)
{
return Mockery::spy(...$args);
}
}
if (! \function_exists('namedMock')) {
/**
* @template TNamedMock of object
*
* @param array<class-string<TNamedMock>|TNamedMock|array<TNamedMock>> $args
*
* @return LegacyMockInterface&MockInterface&TNamedMock
*/
function namedMock(...$args)
{
return Mockery::namedMock(...$args);
}
}
if (! \function_exists('anyArgs')) {
function anyArgs(): AnyArgs
{
return new AnyArgs();
}
}
if (! \function_exists('andAnyOtherArgs')) {
function andAnyOtherArgs(): AndAnyOtherArgs
{
return new AndAnyOtherArgs();
}
}
if (! \function_exists('andAnyOthers')) {
function andAnyOthers(): AndAnyOtherArgs
{
return new AndAnyOtherArgs();
}
}