├── .gitattributes
├── .github
├── dependabot.yml
└── workflows
│ ├── ci-push-pr.yml
│ ├── ci-scheduled.yml
│ ├── ci.yml
│ ├── documentation.yml
│ └── publish-release.yml
├── .gitignore
├── .php-cs-fixer.php
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── MIGRATING.md
├── Makefile
├── README.md
├── composer.json
├── phpstan.neon
├── phpunit.xml
└── src
├── Assertion
├── AssertionRecorder.php
├── AssertionRenderer.php
├── Exception
│ └── AssertionException.php
└── ExceptionAssertionRecorder.php
├── Call
├── Arguments.php
├── Call.php
├── CallData.php
├── CallFactory.php
├── CallVerifier.php
├── CallVerifierFactory.php
├── Event
│ ├── CallEvent.php
│ ├── CallEventFactory.php
│ ├── CallEventTrait.php
│ ├── CalledEvent.php
│ ├── ConsumedEvent.php
│ ├── EndEvent.php
│ ├── IterableEvent.php
│ ├── ProducedEvent.php
│ ├── ReceivedEvent.php
│ ├── ReceivedExceptionEvent.php
│ ├── ResponseEvent.php
│ ├── ReturnedEvent.php
│ ├── ThrewEvent.php
│ └── UsedEvent.php
└── Exception
│ ├── UndefinedArgumentException.php
│ ├── UndefinedCallException.php
│ └── UndefinedResponseException.php
├── Clock
├── Clock.php
└── SystemClock.php
├── Collection
└── NormalizesIndices.php
├── Difference
├── DifferenceEngine.php
└── DifferenceSequenceMatcher.php
├── Event
├── Event.php
├── EventCollection.php
├── EventOrderVerifier.php
├── EventSequence.php
└── Exception
│ └── UndefinedEventException.php
├── Exporter
├── Exporter.php
├── ExporterResult.php
└── InlineExporter.php
├── Facade
├── FacadeContainer.php
├── FacadeContainerTrait.php
├── FacadeTrait.php
└── Globals.php
├── Hamcrest
├── HamcrestMatcher.php
└── HamcrestMatcherDriver.php
├── Hook
├── Exception
│ ├── FunctionExistsException.php
│ ├── FunctionHookException.php
│ ├── FunctionHookGenerationFailedException.php
│ └── FunctionSignatureMismatchException.php
├── FunctionHookGenerator.php
└── FunctionHookManager.php
├── Invocation
├── Invocable.php
├── InvocableInspector.php
├── Invoker.php
├── WrappedInvocable.php
└── WrappedInvocableTrait.php
├── Matcher
├── AnyMatcher.php
├── EqualToMatcher.php
├── Exception
│ └── UndefinedTypeException.php
├── InstanceOfMatcher.php
├── Matcher.php
├── MatcherDriver.php
├── MatcherFactory.php
├── MatcherResult.php
├── MatcherVerifier.php
└── WildcardMatcher.php
├── Mock
├── Builder
│ ├── Method
│ │ ├── CustomMethodDefinition.php
│ │ ├── MethodDefinition.php
│ │ ├── MethodDefinitionCollection.php
│ │ ├── RealMethodDefinition.php
│ │ └── TraitMethodDefinition.php
│ ├── MockBuilder.php
│ ├── MockBuilderFactory.php
│ └── MockDefinition.php
├── Exception
│ ├── AnonymousClassException.php
│ ├── ClassExistsException.php
│ ├── FinalClassException.php
│ ├── FinalMethodStubException.php
│ ├── FinalizedMockException.php
│ ├── InvalidClassNameException.php
│ ├── InvalidDefinitionException.php
│ ├── InvalidMockClassException.php
│ ├── InvalidMockException.php
│ ├── InvalidTypeException.php
│ ├── MockException.php
│ ├── MockGenerationFailedException.php
│ ├── MultipleInheritanceException.php
│ ├── NonMockClassException.php
│ └── UndefinedMethodStubException.php
├── Handle
│ ├── Handle.php
│ ├── HandleFactory.php
│ ├── HandleTrait.php
│ ├── InstanceHandle.php
│ ├── StaticHandle.php
│ └── StaticHandleRegistry.php
├── Method
│ ├── WrappedCustomMethod.php
│ ├── WrappedMagicMethod.php
│ ├── WrappedMethod.php
│ ├── WrappedMethodTrait.php
│ ├── WrappedParentMethod.php
│ ├── WrappedTraitMethod.php
│ └── WrappedUncallableMethod.php
├── Mock.php
├── MockFactory.php
├── MockGenerator.php
└── MockRegistry.php
├── Phony.php
├── Reflection
├── Exception
│ └── UndefinedFeatureException.php
├── FeatureDetector.php
└── FunctionSignatureInspector.php
├── Sequencer
└── Sequencer.php
├── Spy
├── ArraySpy.php
├── Exception
│ ├── NonArrayAccessTraversableException.php
│ └── NonCountableTraversableException.php
├── GeneratorSpyFactory.php
├── GeneratorSpyMap.php
├── IterableSpy.php
├── IterableSpyFactory.php
├── Spy.php
├── SpyData.php
├── SpyFactory.php
├── SpyVerifier.php
├── SpyVerifierFactory.php
└── TraversableSpy.php
├── Stub
├── Answer
│ ├── Answer.php
│ ├── Builder
│ │ ├── GeneratorAnswerBuilder.php
│ │ ├── GeneratorAnswerBuilderFactory.php
│ │ ├── GeneratorYieldFromIteration.php
│ │ └── GeneratorYieldIteration.php
│ └── CallRequest.php
├── EmptyValueFactory.php
├── Exception
│ ├── FinalReturnTypeException.php
│ ├── UndefinedAnswerException.php
│ └── UnusedStubCriteriaException.php
├── Stub.php
├── StubData.php
├── StubFactory.php
├── StubRule.php
├── StubVerifier.php
└── StubVerifierFactory.php
├── Verification
├── Cardinality.php
├── CardinalityVerifier.php
├── CardinalityVerifierTrait.php
├── Exception
│ ├── InvalidCardinalityException.php
│ ├── InvalidCardinalityStateException.php
│ └── InvalidSingularCardinalityException.php
├── GeneratorVerifier.php
├── GeneratorVerifierFactory.php
├── IterableVerifier.php
└── IterableVerifierFactory.php
├── functions.php
└── initialize.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | /assets/ export-ignore
2 | /doc/ export-ignore
3 | /scripts/ export-ignore
4 | /test/ export-ignore
5 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: composer
4 | schedule:
5 | interval: weekly
6 | reviewers:
7 | - eloquent/dependabot-reviewers
8 | - package-ecosystem: github-actions
9 | schedule:
10 | interval: weekly
11 | reviewers:
12 | - eloquent/dependabot-reviewers
13 |
--------------------------------------------------------------------------------
/.github/workflows/ci-push-pr.yml:
--------------------------------------------------------------------------------
1 | name: CI (push / PR)
2 | on:
3 | push:
4 | pull_request:
5 | jobs:
6 | CI:
7 | name: CI
8 | uses: ./.github/workflows/ci.yml
9 | secrets: inherit
10 | with:
11 | publish-coverage: true
12 |
--------------------------------------------------------------------------------
/.github/workflows/ci-scheduled.yml:
--------------------------------------------------------------------------------
1 | name: CI (scheduled)
2 | on:
3 | schedule:
4 | - cron: 0 14 * * 0 # Sunday 2PM UTC = Monday 12AM AEST
5 | jobs:
6 | CI:
7 | name: CI
8 | uses: ./.github/workflows/ci.yml
9 | secrets: inherit
10 | with:
11 | publish-coverage: false
12 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | workflow_call:
4 | inputs:
5 | publish-coverage:
6 | type: boolean
7 | required: true
8 | jobs:
9 | CI:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | php: ["8.0", "8.1", "8.2"]
15 | name: PHP ${{ matrix.php }}
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v1
19 | - name: Set up PHP
20 | uses: shivammathur/setup-php@v2
21 | with:
22 | php-version: ${{ matrix.php }}
23 | extensions: mbstring
24 | coverage: pcov
25 | - name: Install dependencies
26 | run: make vendor
27 | - name: Make
28 | run: make ci
29 | - name: Publish coverage reports
30 | if: inputs.publish-coverage && success()
31 | uses: codecov/codecov-action@v3
32 |
--------------------------------------------------------------------------------
/.github/workflows/documentation.yml:
--------------------------------------------------------------------------------
1 | name: Documentation
2 | on:
3 | push:
4 | branches:
5 | - main
6 | tags:
7 | - "[0-9]+.[0-9]+.[0-9]+"
8 | jobs:
9 | documentation:
10 | runs-on: ubuntu-latest
11 | name: Publish documentation
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v1
15 | - name: Set up PHP
16 | uses: shivammathur/setup-php@v2
17 | with:
18 | php-version: "8.2"
19 | extensions: mbstring
20 | coverage: none
21 | - name: Install dependencies
22 | run: make vendor
23 | - name: Publish documentation
24 | if: success()
25 | run: make doc-publish
26 | env:
27 | DOC_GITHUB_TOKEN: ${{ secrets.DOC_GITHUB_TOKEN }}
28 |
--------------------------------------------------------------------------------
/.github/workflows/publish-release.yml:
--------------------------------------------------------------------------------
1 | name: Publish release
2 | on:
3 | push:
4 | tags:
5 | - "*"
6 | jobs:
7 | publish:
8 | runs-on: ubuntu-latest
9 | name: Publish release
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v3
13 | - name: Publish release
14 | uses: eloquent/github-release-action@v3
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.makefiles/
2 | /artifacts/
3 | /composer.lock
4 | /vendor/
5 |
--------------------------------------------------------------------------------
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | setCacheFile(__DIR__ . '/artifacts/lint/php-cs-fixer/cache');
5 | $config->setRules(array_merge($config->getRules(), [
6 | 'phpdoc_to_comment' => false,
7 | 'no_blank_lines_after_phpdoc' => false,
8 | ]));
9 |
10 | $exclusions = [
11 | 'artifacts',
12 | 'test/fixture',
13 | ];
14 |
15 | if (version_compare(PHP_VERSION, '8.1.x', '<')) {
16 | $exclusions[] = 'test/src/Test/Enum';
17 | $exclusions[] = 'test/src/Test/Php81';
18 | }
19 |
20 | if (version_compare(PHP_VERSION, '8.2.x', '<')) {
21 | $exclusions[] = 'test/src/Test/Php82';
22 | }
23 |
24 | $config->getFinder()->exclude($exclusions);
25 |
26 | return $config;
27 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | As a guideline, please follow this process when contributing:
4 |
5 | 1. [Fork the repository]
6 | 2. [Create a branch]
7 | 3. Make your changes
8 | 4. Use `make prepare` to run tests and code style checks
9 | 5. [Squash commits] if necessary
10 | 6. [Create a pull request]
11 |
12 | [create a branch]: https://help.github.com/articles/about-branches
13 | [create a pull request]: https://help.github.com/articles/creating-a-pull-request
14 | [fork the repository]: https://help.github.com/articles/fork-a-repo
15 | [squash commits]: https://help.github.com/articles/about-git-rebase
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright © 2021 Erin Millard
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Powered by https://makefiles.dev/
2 |
3 | export PHP_CS_FIXER_IGNORE_ENV=true
4 |
5 | ################################################################################
6 |
7 | _HOOK_FIXTURE_INPUT_FILES := $(shell find test/fixture/hook-generator -name callback.php)
8 | _HOOK_FIXTURE_OUTPUT_FILES := $(_HOOK_FIXTURE_INPUT_FILES:callback.php=expected.php)
9 |
10 | _MOCK_FIXTURE_INPUT_FILES := $(shell find test/fixture/mock-generator -name builder.php)
11 | _MOCK_FIXTURE_OUTPUT_FILES := $(_MOCK_FIXTURE_INPUT_FILES:builder.php=expected.php)
12 |
13 | _VERIFICATION_FIXTURE_INPUT_FILES := $(shell find test/fixture/verification -name verification.php)
14 | _VERIFICATION_FIXTURE_OUTPUT_FILES := $(_VERIFICATION_FIXTURE_INPUT_FILES:verification.php=expected)
15 | _VERIFICATION_IMAGE_FILES := $(_VERIFICATION_FIXTURE_INPUT_FILES:test/fixture/verification/%/verification.php=artifacts/build/doc-img/%.svg)
16 |
17 | _DOC_MARKDOWN_FILES := $(wildcard doc/*.md)
18 | _DOC_HTML_FILES := $(_DOC_MARKDOWN_FILES:doc/%.md=artifacts/build/doc-html/%.html)
19 |
20 | GENERATED_FILES += $(_HOOK_FIXTURE_OUTPUT_FILES) $(_MOCK_FIXTURE_OUTPUT_FILES) $(_VERIFICATION_FIXTURE_OUTPUT_FILES)
21 |
22 | ################################################################################
23 |
24 | -include .makefiles/Makefile
25 | -include .makefiles/pkg/php/v1/Makefile
26 |
27 | .makefiles/%:
28 | @curl -sfL https://makefiles.dev/v1 | bash /dev/stdin "$@"
29 |
30 | ################################################################################
31 |
32 | .PHONY: doc
33 | doc: artifacts/build/gh-pages
34 |
35 | .PHONY: doc-open
36 | doc-open:
37 | open http://localhost:8080/
38 |
39 | .PHONY: doc-publish
40 | doc-publish: artifacts/build/gh-pages
41 | scripts/publish-doc "$<"
42 |
43 | .PHONY: doc-serve
44 | doc-serve: artifacts/build/gh-pages
45 | php -S 0.0.0.0:8080 -t "$<" assets/router.php
46 |
47 | .PHONY: output-examples
48 | output-examples: vendor
49 | scripts/output-examples
50 |
51 | .PHONY: test-edge-cases
52 | test-edge-cases: artifacts/test/edge-cases.touch
53 |
54 | .PHONY: test-integration
55 | test-integration: artifacts/test/integration.touch
56 |
57 | ################################################################################
58 |
59 | artifacts/build/doc-html/%.html: doc/%.md vendor $(wildcard assets/web/*.tpl.html)
60 | @mkdir -p "$(@D)"
61 | scripts/gfm-to-html "$<" "$@"
62 |
63 | artifacts/build/doc-img/%.svg: test/fixture/verification/%/verification.php $(wildcard assets/svg/*.tpl.svg) vendor $(PHP_SOURCE_FILES)
64 | @mkdir -p "$(@D)"
65 | scripts/build-doc-img "$<" "$@"
66 |
67 | artifacts/build/doc: $(wildcard assets/web/css/* assets/web/data/* assets/web/img/* assets/web/js/*) $(_DOC_HTML_FILES) $(_VERIFICATION_IMAGE_FILES)
68 | @rm -rf "$@"
69 | @mkdir -p "$@/img/verification"
70 | @cp -av assets/web/css assets/web/data assets/web/img assets/web/js artifacts/build/doc-html/*.html "$@/"
71 | @cp -av artifacts/build/doc-img/* "$@/img/verification/"
72 |
73 | artifacts/build/gh-pages: artifacts/build/doc artifacts/build/gh-pages-clone vendor $(wildcard assets/web/*.tpl.html)
74 | scripts/refresh-git-clone artifacts/build/gh-pages-clone
75 | @rm -rf "$@"
76 | cp -a artifacts/build/gh-pages-clone "$@"
77 | scripts/update-gh-pages "$<" "$@"
78 |
79 | artifacts/build/gh-pages-clone:
80 | git clone -b gh-pages --single-branch --depth 1 https://github.com/eloquent/phony.git "$@"
81 |
82 | artifacts/test/edge-cases.touch: $(PHP_PHPUNIT_REQ) $(_PHP_PHPUNIT_REQ)
83 | php $(_PHP_PHPUNIT_RUNTIME_ARGS) vendor/bin/phpunit $(_PHP_PHPUNIT_ARGS) --no-coverage test/suite-edge-cases
84 |
85 | @mkdir -p "$(@D)"
86 | @touch "$@"
87 |
88 | artifacts/test/integration.touch: vendor $(PHP_SOURCE_FILES) $(_PHP_TEST_ASSETS)
89 | test/integration/run-all
90 |
91 | @mkdir -p "$(@D)"
92 | @touch "$@"
93 |
94 | test/fixture/hook-generator/%/expected.php: | test/fixture/hook-generator/%/callback.php
95 | scripts/build-hook-generator-fixture "$|" "$@"
96 |
97 | test/fixture/mock-generator/%/expected.php: | test/fixture/mock-generator/%/builder.php
98 | scripts/build-mock-generator-fixture "$|" "$@"
99 |
100 | test/fixture/verification/%/expected: | test/fixture/verification/%/verification.php
101 | scripts/build-verification-fixture "$|" "$@"
102 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eloquent/phony",
3 | "description": "Mocks, stubs, and spies for PHP.",
4 | "keywords": ["mock", "mocking", "stub", "stubbing", "spy", "dummy", "double", "test", "fake"],
5 | "homepage": "https://eloquent-software.com/phony/",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Erin Millard",
10 | "email": "ezzatron@gmail.com",
11 | "homepage": "https://ezzatron.com/"
12 | }
13 | ],
14 | "config": {
15 | "allow-plugins": {
16 | "phpstan/extension-installer": true
17 | }
18 | },
19 | "require": {
20 | "php": "^8"
21 | },
22 | "require-dev": {
23 | "ext-pdo": "*",
24 | "eloquent/code-style": "^2",
25 | "eloquent/phpstan-phony": "^0.8",
26 | "friendsofphp/php-cs-fixer": "^3",
27 | "hamcrest/hamcrest-php": "^2",
28 | "phpstan/extension-installer": "^1",
29 | "phpstan/phpstan": "^1",
30 | "phpstan/phpstan-phpunit": "^1",
31 | "phpunit/phpunit": "^9"
32 | },
33 | "autoload": {
34 | "psr-4": {
35 | "Eloquent\\Phony\\": "src"
36 | },
37 | "files": [
38 | "src/initialize.php",
39 | "src/functions.php"
40 | ]
41 | },
42 | "autoload-dev": {
43 | "psr-4": {
44 | "Eloquent\\Phony\\": ["test/src"]
45 | },
46 | "files": [
47 | "test/src/ClassA.php",
48 | "test/src/ClassWithProperty.php",
49 | "test/src/initialize.php",
50 | "test/src/Test/functions.php",
51 | "test/src/TestClass.php"
52 | ]
53 | },
54 | "extra": {
55 | "branch-alias": {
56 | "dev-main": "5.1.x-dev"
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: max
3 | paths:
4 | - src
5 | ignoreErrors:
6 | # allow @throws with interfaces
7 | - message: "/@throws with type .* is not subtype of Throwable/"
8 | paths:
9 | - src/Call/CallData.php
10 | - src/Call/CallVerifier.php
11 | - src/Facade/FacadeTrait.php
12 | - src/functions.php
13 | - src/Hook/FunctionHookManager.php
14 | - src/Invocation/WrappedInvocableTrait.php
15 | - src/Mock/Builder/MockBuilder.php
16 | - src/Mock/Handle/Handle.php
17 | - src/Mock/Handle/HandleFactory.php
18 | - src/Mock/Handle/HandleTrait.php
19 | - src/Mock/MockFactory.php
20 | - src/Verification/Cardinality.php
21 | - src/Verification/CardinalityVerifier.php
22 | - src/Verification/CardinalityVerifierTrait.php
23 | # allow testing for class existence with ReflectionClass constructor
24 | - message: "/class ReflectionClass constructor expects class-string.* string given/"
25 | paths:
26 | - src/Mock/Builder/MockBuilder.php
27 | - src/Mock/Handle/HandleFactory.php
28 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | test/suite
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | src
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Assertion/AssertionRecorder.php:
--------------------------------------------------------------------------------
1 | $events The events.
30 | *
31 | * @return EventCollection The result.
32 | */
33 | public function createSuccess(array $events = []): EventCollection;
34 |
35 | /**
36 | * Record that a successful assertion occurred.
37 | *
38 | * @param EventCollection $events The events.
39 | *
40 | * @return EventCollection The result.
41 | */
42 | public function createSuccessFromEventCollection(
43 | EventCollection $events
44 | ): EventCollection;
45 |
46 | /**
47 | * Create a new assertion failure exception.
48 | *
49 | * @param string $description The failure description.
50 | *
51 | * @return null If this recorder does not throw exceptions.
52 | * @throws Throwable If this recorder throws exceptions.
53 | */
54 | public function createFailure(string $description);
55 | }
56 |
--------------------------------------------------------------------------------
/src/Assertion/Exception/AssertionException.php:
--------------------------------------------------------------------------------
1 | getProperty('trace');
28 | $traceProperty->setAccessible(true);
29 | /** @var array $trace */
30 | $trace = $traceProperty->getValue($exception);
31 | $fileProperty = $reflector->getProperty('file');
32 | $fileProperty->setAccessible(true);
33 | $lineProperty = $reflector->getProperty('line');
34 | $lineProperty->setAccessible(true);
35 |
36 | $call = static::tracePhonyCall($trace);
37 |
38 | if (empty($call)) {
39 | $traceProperty->setValue($exception, []);
40 | $fileProperty->setValue($exception, '');
41 | $lineProperty->setValue($exception, 0);
42 | } else {
43 | $traceProperty->setValue($exception, [$call]);
44 | $fileProperty->setValue($exception, $call['file'] ?? '');
45 | $lineProperty->setValue($exception, $call['line'] ?? 0);
46 | }
47 | }
48 |
49 | /**
50 | * Find the Phony entry point call in a stack trace.
51 | *
52 | * @param array $trace The stack trace.
53 | *
54 | * @return array The call, or an empty array if unable to determine the entry point.
55 | */
56 | public static function tracePhonyCall(array $trace): array
57 | {
58 | $prefix = 'Eloquent\Phony\\';
59 |
60 | for ($i = count($trace) - 1; $i >= 0; --$i) {
61 | $entry = $trace[$i];
62 |
63 | if (isset($entry['class'])) {
64 | if (0 === strpos($entry['class'], $prefix)) {
65 | return $entry;
66 | }
67 | } elseif (0 === strpos($entry['function'], $prefix)) {
68 | return $entry;
69 | }
70 | }
71 |
72 | return [];
73 | }
74 |
75 | /**
76 | * Construct a new assertion exception.
77 | *
78 | * @param string $description The failure description.
79 | */
80 | public function __construct(string $description)
81 | {
82 | parent::__construct($description);
83 |
84 | static::trim($this);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Assertion/ExceptionAssertionRecorder.php:
--------------------------------------------------------------------------------
1 | callVerifierFactory = $callVerifierFactory;
27 | }
28 |
29 | /**
30 | * Record that a successful assertion occurred.
31 | *
32 | * @param array $events The events.
33 | *
34 | * @return EventCollection The result.
35 | */
36 | public function createSuccess(array $events = []): EventCollection
37 | {
38 | return new EventSequence($events, $this->callVerifierFactory);
39 | }
40 |
41 | /**
42 | * Record that a successful assertion occurred.
43 | *
44 | * @param EventCollection $events The events.
45 | *
46 | * @return EventCollection The result.
47 | */
48 | public function createSuccessFromEventCollection(
49 | EventCollection $events
50 | ): EventCollection {
51 | return $events;
52 | }
53 |
54 | /**
55 | * Create a new assertion failure exception.
56 | *
57 | * @param string $description The failure description.
58 | *
59 | * @return null This method never returns.
60 | * @throws AssertionException The assertion failure exception.
61 | */
62 | public function createFailure(string $description)
63 | {
64 | throw new AssertionException($description);
65 | }
66 |
67 | /**
68 | * @var CallVerifierFactory
69 | */
70 | private $callVerifierFactory;
71 | }
72 |
--------------------------------------------------------------------------------
/src/Call/CallFactory.php:
--------------------------------------------------------------------------------
1 | eventFactory = $eventFactory;
28 | $this->invoker = $invoker;
29 | }
30 |
31 | /**
32 | * Record call details by invoking a callback.
33 | *
34 | * @param callable $callback The callback.
35 | * @param Arguments $arguments The arguments.
36 | * @param SpyData $spy The spy to record the call to.
37 | *
38 | * @return CallData The newly created call.
39 | */
40 | public function record(
41 | callable $callback,
42 | Arguments $arguments,
43 | SpyData $spy
44 | ): CallData {
45 | $originalArguments = $arguments->copy();
46 |
47 | $call = new CallData(
48 | $spy->nextIndex(),
49 | $this->eventFactory->createCalled($spy, $originalArguments)
50 | );
51 | $spy->addCall($call);
52 |
53 | $returnValue = null;
54 | $exception = null;
55 |
56 | try {
57 | $returnValue = $this->invoker->callWith($callback, $arguments);
58 | } catch (Throwable $exception) {
59 | // handled below
60 | }
61 |
62 | if ($exception) {
63 | $responseEvent = $this->eventFactory->createThrew($exception);
64 | } else {
65 | $responseEvent = $this->eventFactory->createReturned($returnValue);
66 | }
67 |
68 | $call->setResponseEvent($responseEvent);
69 |
70 | return $call;
71 | }
72 |
73 | /**
74 | * @var CallEventFactory
75 | */
76 | private $eventFactory;
77 |
78 | /**
79 | * @var Invoker
80 | */
81 | private $invoker;
82 | }
83 |
--------------------------------------------------------------------------------
/src/Call/CallVerifierFactory.php:
--------------------------------------------------------------------------------
1 | matcherFactory = $matcherFactory;
38 | $this->matcherVerifier = $matcherVerifier;
39 | $this->generatorVerifierFactory = $generatorVerifierFactory;
40 | $this->iterableVerifierFactory = $iterableVerifierFactory;
41 | $this->assertionRecorder = $assertionRecorder;
42 | $this->assertionRenderer = $assertionRenderer;
43 | }
44 |
45 | /**
46 | * Wrap the supplied call in a verifier.
47 | *
48 | * @param Call $call The call.
49 | *
50 | * @return CallVerifier The call verifier.
51 | */
52 | public function fromCall(Call $call): CallVerifier
53 | {
54 | return new CallVerifier(
55 | $call,
56 | $this->matcherFactory,
57 | $this->matcherVerifier,
58 | $this->generatorVerifierFactory,
59 | $this->iterableVerifierFactory,
60 | $this->assertionRecorder,
61 | $this->assertionRenderer
62 | );
63 | }
64 |
65 | /**
66 | * Wrap the supplied calls in verifiers.
67 | *
68 | * @param array $calls The calls.
69 | *
70 | * @return array The call verifiers.
71 | */
72 | public function fromCalls(array $calls): array
73 | {
74 | $verifiers = [];
75 |
76 | foreach ($calls as $call) {
77 | $verifiers[] = new CallVerifier(
78 | $call,
79 | $this->matcherFactory,
80 | $this->matcherVerifier,
81 | $this->generatorVerifierFactory,
82 | $this->iterableVerifierFactory,
83 | $this->assertionRecorder,
84 | $this->assertionRenderer
85 | );
86 | }
87 |
88 | return $verifiers;
89 | }
90 |
91 | /**
92 | * @var MatcherFactory
93 | */
94 | private $matcherFactory;
95 |
96 | /**
97 | * @var MatcherVerifier
98 | */
99 | private $matcherVerifier;
100 |
101 | /**
102 | * @var GeneratorVerifierFactory
103 | */
104 | private $generatorVerifierFactory;
105 |
106 | /**
107 | * @var IterableVerifierFactory
108 | */
109 | private $iterableVerifierFactory;
110 |
111 | /**
112 | * @var AssertionRecorder
113 | */
114 | private $assertionRecorder;
115 |
116 | /**
117 | * @var AssertionRenderer
118 | */
119 | private $assertionRenderer;
120 | }
121 |
--------------------------------------------------------------------------------
/src/Call/Event/CallEvent.php:
--------------------------------------------------------------------------------
1 | sequencer = $sequencer;
26 | $this->clock = $clock;
27 | }
28 |
29 | /**
30 | * Create a new 'called' event.
31 | *
32 | * @param callable $callback The callback.
33 | * @param Arguments $arguments The arguments.
34 | *
35 | * @return CalledEvent The newly created event.
36 | */
37 | public function createCalled(
38 | callable $callback,
39 | Arguments $arguments
40 | ): CalledEvent {
41 | return new CalledEvent(
42 | $this->sequencer->next(),
43 | $this->clock->time(),
44 | $callback,
45 | $arguments
46 | );
47 | }
48 |
49 | /**
50 | * Create a new 'returned' event.
51 | *
52 | * @param mixed $value The return value.
53 | *
54 | * @return ReturnedEvent The newly created event.
55 | */
56 | public function createReturned($value): ReturnedEvent
57 | {
58 | return new ReturnedEvent(
59 | $this->sequencer->next(),
60 | $this->clock->time(),
61 | $value
62 | );
63 | }
64 |
65 | /**
66 | * Create a new 'thrown' event.
67 | *
68 | * @param Throwable $exception The thrown exception.
69 | *
70 | * @return ThrewEvent The newly created event.
71 | */
72 | public function createThrew(Throwable $exception): ThrewEvent
73 | {
74 | return new ThrewEvent(
75 | $this->sequencer->next(),
76 | $this->clock->time(),
77 | $exception
78 | );
79 | }
80 |
81 | /**
82 | * Create a new 'used' event.
83 | *
84 | * @return UsedEvent The newly created event.
85 | */
86 | public function createUsed(): UsedEvent
87 | {
88 | return new UsedEvent($this->sequencer->next(), $this->clock->time());
89 | }
90 |
91 | /**
92 | * Create a new 'produced' event.
93 | *
94 | * @param mixed $key The produced key.
95 | * @param mixed $value The produced value.
96 | *
97 | * @return ProducedEvent The newly created event.
98 | */
99 | public function createProduced($key, $value): ProducedEvent
100 | {
101 | return new ProducedEvent(
102 | $this->sequencer->next(),
103 | $this->clock->time(),
104 | $key,
105 | $value
106 | );
107 | }
108 |
109 | /**
110 | * Create a new 'received' event.
111 | *
112 | * @param mixed $value The received value.
113 | *
114 | * @return ReceivedEvent The newly created event.
115 | */
116 | public function createReceived($value): ReceivedEvent
117 | {
118 | return new ReceivedEvent(
119 | $this->sequencer->next(),
120 | $this->clock->time(),
121 | $value
122 | );
123 | }
124 |
125 | /**
126 | * Create a new 'received exception' event.
127 | *
128 | * @param Throwable $exception The received exception.
129 | *
130 | * @return ReceivedExceptionEvent The newly created event.
131 | */
132 | public function createReceivedException(
133 | Throwable $exception
134 | ): ReceivedExceptionEvent {
135 | return new ReceivedExceptionEvent(
136 | $this->sequencer->next(),
137 | $this->clock->time(),
138 | $exception
139 | );
140 | }
141 |
142 | /**
143 | * Create a new 'consumed' event.
144 | *
145 | * @return ConsumedEvent The newly created event.
146 | */
147 | public function createConsumed(): ConsumedEvent
148 | {
149 | return new ConsumedEvent(
150 | $this->sequencer->next(),
151 | $this->clock->time()
152 | );
153 | }
154 |
155 | /**
156 | * @var Sequencer
157 | */
158 | private $sequencer;
159 |
160 | /**
161 | * @var Clock
162 | */
163 | private $clock;
164 | }
165 |
--------------------------------------------------------------------------------
/src/Call/Event/CallEventTrait.php:
--------------------------------------------------------------------------------
1 | sequenceNumber;
26 | }
27 |
28 | /**
29 | * Get the time at which the event occurred.
30 | *
31 | * @return float The time at which the event occurred, in seconds since the Unix epoch.
32 | */
33 | public function time(): float
34 | {
35 | return $this->time;
36 | }
37 |
38 | /**
39 | * Set the call.
40 | *
41 | * @param Call $call The call.
42 | *
43 | * @return $this This event.
44 | */
45 | public function setCall(Call $call): CallEvent
46 | {
47 | $this->call = $call;
48 |
49 | return $this;
50 | }
51 |
52 | /**
53 | * Get the call.
54 | *
55 | * @return ?Call The call, or null if no call has been set.
56 | */
57 | public function call(): ?Call
58 | {
59 | return $this->call;
60 | }
61 |
62 | /**
63 | * @var int
64 | */
65 | private $sequenceNumber;
66 |
67 | /**
68 | * @var float
69 | */
70 | private $time;
71 |
72 | /**
73 | * @var ?Call
74 | */
75 | private $call;
76 | }
77 |
--------------------------------------------------------------------------------
/src/Call/Event/CalledEvent.php:
--------------------------------------------------------------------------------
1 | sequenceNumber = $sequenceNumber;
31 | $this->time = $time;
32 | $this->callback = $callback;
33 | $this->arguments = $arguments;
34 | }
35 |
36 | /**
37 | * Get the callback.
38 | *
39 | * @return callable The callback.
40 | */
41 | public function callback(): callable
42 | {
43 | return $this->callback;
44 | }
45 |
46 | /**
47 | * Get the received arguments.
48 | *
49 | * @return Arguments The received arguments.
50 | */
51 | public function arguments(): Arguments
52 | {
53 | return $this->arguments;
54 | }
55 |
56 | /**
57 | * @var callable
58 | */
59 | private $callback;
60 |
61 | /**
62 | * @var Arguments
63 | */
64 | private $arguments;
65 | }
66 |
--------------------------------------------------------------------------------
/src/Call/Event/ConsumedEvent.php:
--------------------------------------------------------------------------------
1 | sequenceNumber = $sequenceNumber;
23 | $this->time = $time;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Call/Event/EndEvent.php:
--------------------------------------------------------------------------------
1 | sequenceNumber = $sequenceNumber;
25 | $this->time = $time;
26 | $this->key = $key;
27 | $this->value = $value;
28 | }
29 |
30 | /**
31 | * Get the produced key.
32 | *
33 | * @return mixed The produced key.
34 | */
35 | public function key()
36 | {
37 | return $this->key;
38 | }
39 |
40 | /**
41 | * Get the produced value.
42 | *
43 | * @return mixed The produced value.
44 | */
45 | public function value()
46 | {
47 | return $this->value;
48 | }
49 |
50 | /**
51 | * @var mixed
52 | */
53 | private $key;
54 |
55 | /**
56 | * @var mixed
57 | */
58 | private $value;
59 | }
60 |
--------------------------------------------------------------------------------
/src/Call/Event/ReceivedEvent.php:
--------------------------------------------------------------------------------
1 | sequenceNumber = $sequenceNumber;
24 | $this->time = $time;
25 | $this->value = $value;
26 | }
27 |
28 | /**
29 | * Get the received value.
30 | *
31 | * @return mixed The received value.
32 | */
33 | public function value()
34 | {
35 | return $this->value;
36 | }
37 |
38 | /**
39 | * @var mixed
40 | */
41 | private $value;
42 | }
43 |
--------------------------------------------------------------------------------
/src/Call/Event/ReceivedExceptionEvent.php:
--------------------------------------------------------------------------------
1 | sequenceNumber = $sequenceNumber;
29 | $this->time = $time;
30 | $this->exception = $exception;
31 | }
32 |
33 | /**
34 | * Get the received exception.
35 | *
36 | * @return Throwable The received exception.
37 | */
38 | public function exception(): Throwable
39 | {
40 | return $this->exception;
41 | }
42 |
43 | /**
44 | * @var Throwable
45 | */
46 | private $exception;
47 | }
48 |
--------------------------------------------------------------------------------
/src/Call/Event/ResponseEvent.php:
--------------------------------------------------------------------------------
1 | sequenceNumber = $sequenceNumber;
24 | $this->time = $time;
25 | $this->value = $value;
26 | }
27 |
28 | /**
29 | * Get the returned value.
30 | *
31 | * @return mixed The returned value.
32 | */
33 | public function value()
34 | {
35 | return $this->value;
36 | }
37 |
38 | /**
39 | * @var mixed
40 | */
41 | private $value;
42 | }
43 |
--------------------------------------------------------------------------------
/src/Call/Event/ThrewEvent.php:
--------------------------------------------------------------------------------
1 | sequenceNumber = $sequenceNumber;
29 | $this->time = $time;
30 | $this->exception = $exception;
31 | }
32 |
33 | /**
34 | * Get the thrown exception.
35 | *
36 | * @return Throwable The thrown exception.
37 | */
38 | public function exception(): Throwable
39 | {
40 | return $this->exception;
41 | }
42 |
43 | /**
44 | * @var Throwable
45 | */
46 | private $exception;
47 | }
48 |
--------------------------------------------------------------------------------
/src/Call/Event/UsedEvent.php:
--------------------------------------------------------------------------------
1 | sequenceNumber = $sequenceNumber;
23 | $this->time = $time;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Call/Exception/UndefinedArgumentException.php:
--------------------------------------------------------------------------------
1 | index = $index;
22 |
23 | parent::__construct(
24 | sprintf(
25 | 'No argument defined for index %s.',
26 | var_export($index, true)
27 | )
28 | );
29 | }
30 |
31 | /**
32 | * Get the index.
33 | *
34 | * @return int The index.
35 | */
36 | public function index(): int
37 | {
38 | return $this->index;
39 | }
40 |
41 | /**
42 | * @var int
43 | */
44 | private $index;
45 | }
46 |
--------------------------------------------------------------------------------
/src/Call/Exception/UndefinedCallException.php:
--------------------------------------------------------------------------------
1 | index = $index;
22 |
23 | parent::__construct(
24 | sprintf('No call defined for index %s.', var_export($index, true))
25 | );
26 | }
27 |
28 | /**
29 | * Get the call index.
30 | *
31 | * @return int The call index.
32 | */
33 | public function index(): int
34 | {
35 | return $this->index;
36 | }
37 |
38 | /**
39 | * @var int
40 | */
41 | private $index;
42 | }
43 |
--------------------------------------------------------------------------------
/src/Call/Exception/UndefinedResponseException.php:
--------------------------------------------------------------------------------
1 | microtime = $microtime;
20 | }
21 |
22 | /**
23 | * Get the current time.
24 | *
25 | * @return float The current time.
26 | */
27 | public function time(): float
28 | {
29 | $microtime = $this->microtime;
30 |
31 | return $microtime(true);
32 | }
33 |
34 | /**
35 | * @var callable
36 | */
37 | private $microtime;
38 | }
39 |
--------------------------------------------------------------------------------
/src/Collection/NormalizesIndices.php:
--------------------------------------------------------------------------------
1 | = $size) {
30 | return false;
31 | }
32 |
33 | $normalized = $potential;
34 |
35 | return true;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Difference/DifferenceEngine.php:
--------------------------------------------------------------------------------
1 | featureDetector = $featureDetector;
22 |
23 | $this->setUseColor(null);
24 | }
25 |
26 | /**
27 | * Turn on or off the use of ANSI colored output.
28 | *
29 | * Pass `null` to detect automatically.
30 | *
31 | * @param ?bool $useColor True to use color.
32 | */
33 | public function setUseColor(?bool $useColor): void
34 | {
35 | if (null === $useColor) {
36 | $useColor = $this->featureDetector->isSupported('stdout.ansi');
37 | }
38 |
39 | // @codeCoverageIgnoreStart
40 | if ($useColor) {
41 | $this->addStart = "\033[33m\033[2m{+\033[0m\033[33m\033[4m";
42 | $this->addEnd = "\033[0m\033[33m\033[2m+}\033[0m";
43 | $this->removeStart = "\033[36m\033[2m[-\033[0m\033[36m\033[4m";
44 | $this->removeEnd = "\033[0m\033[36m\033[2m-]\033[0m";
45 | } else {
46 | // @codeCoverageIgnoreEnd
47 | $this->addStart = '{+';
48 | $this->addEnd = '+}';
49 | $this->removeStart = '[-';
50 | $this->removeEnd = '-]';
51 | }
52 | }
53 |
54 | /**
55 | * Get the difference between the supplied strings.
56 | *
57 | * @param string $from The from value.
58 | * @param string $to The to value.
59 | *
60 | * @return string The difference.
61 | */
62 | public function difference(string $from, string $to): string
63 | {
64 | $flags = PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY;
65 | /** @var array */
66 | $from = preg_split('/(\W+)/u', $from, -1, $flags);
67 | /** @var array */
68 | $to = preg_split('/(\W+)/u', $to, -1, $flags);
69 |
70 | $matcher = new DifferenceSequenceMatcher($from, $to);
71 | $diff = '';
72 |
73 | foreach ($matcher->getOpcodes() as $opcode) {
74 | list($tag, $i1, $i2, $j1, $j2) = $opcode;
75 |
76 | if ($tag === 'equal') {
77 | $diff .= implode(array_slice($from, $i1, $i2 - $i1));
78 | } else {
79 | if ($tag === 'replace' || $tag === 'delete') {
80 | $diff .=
81 | $this->removeStart .
82 | implode(array_slice($from, $i1, $i2 - $i1)) .
83 | $this->removeEnd;
84 | }
85 |
86 | if ($tag === 'replace' || $tag === 'insert') {
87 | $diff .=
88 | $this->addStart .
89 | implode(array_slice($to, $j1, $j2 - $j1)) .
90 | $this->addEnd;
91 | }
92 | }
93 | }
94 |
95 | return $diff;
96 | }
97 |
98 | /**
99 | * @var FeatureDetector
100 | */
101 | private $featureDetector;
102 |
103 | /**
104 | * @var string
105 | */
106 | private $addStart;
107 |
108 | /**
109 | * @var string
110 | */
111 | private $addEnd;
112 |
113 | /**
114 | * @var string
115 | */
116 | private $removeStart;
117 |
118 | /**
119 | * @var string
120 | */
121 | private $removeEnd;
122 | }
123 |
--------------------------------------------------------------------------------
/src/Event/Event.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | interface EventCollection extends IteratorAggregate, Countable
19 | {
20 | /**
21 | * Returns true if this collection contains any events.
22 | *
23 | * @return bool True if this collection contains any events.
24 | */
25 | public function hasEvents(): bool;
26 |
27 | /**
28 | * Returns true if this collection contains any calls.
29 | *
30 | * @return bool True if this collection contains any calls.
31 | */
32 | public function hasCalls(): bool;
33 |
34 | /**
35 | * Get the number of events.
36 | *
37 | * @return int The event count.
38 | */
39 | public function eventCount(): int;
40 |
41 | /**
42 | * Get the number of calls.
43 | *
44 | * @return int The call count.
45 | */
46 | public function callCount(): int;
47 |
48 | /**
49 | * Get all events as an array.
50 | *
51 | * @return array The events.
52 | */
53 | public function allEvents(): array;
54 |
55 | /**
56 | * Get all calls as an array.
57 | *
58 | * @return array The calls.
59 | */
60 | public function allCalls(): array;
61 |
62 | /**
63 | * Get the first event.
64 | *
65 | * @return Event The event.
66 | * @throws UndefinedEventException If there are no events.
67 | */
68 | public function firstEvent(): Event;
69 |
70 | /**
71 | * Get the last event.
72 | *
73 | * @return Event The event.
74 | * @throws UndefinedEventException If there are no events.
75 | */
76 | public function lastEvent(): Event;
77 |
78 | /**
79 | * Get an event by index.
80 | *
81 | * Negative indices are offset from the end of the list. That is, `-1`
82 | * indicates the last element, and `-2` indicates the second last element.
83 | *
84 | * @param int $index The index.
85 | *
86 | * @return Event The event.
87 | * @throws UndefinedEventException If the requested event is undefined, or there are no events.
88 | */
89 | public function eventAt(int $index = 0): Event;
90 |
91 | /**
92 | * Get the first call.
93 | *
94 | * @return Call The call.
95 | * @throws UndefinedCallException If there are no calls.
96 | */
97 | public function firstCall(): Call;
98 |
99 | /**
100 | * Get the last call.
101 | *
102 | * @return Call The call.
103 | * @throws UndefinedCallException If there are no calls.
104 | */
105 | public function lastCall(): Call;
106 |
107 | /**
108 | * Get a call by index.
109 | *
110 | * Negative indices are offset from the end of the list. That is, `-1`
111 | * indicates the last element, and `-2` indicates the second last element.
112 | *
113 | * @param int $index The index.
114 | *
115 | * @return Call The call.
116 | * @throws UndefinedCallException If the requested call is undefined, or there are no calls.
117 | */
118 | public function callAt(int $index = 0): Call;
119 | }
120 |
--------------------------------------------------------------------------------
/src/Event/Exception/UndefinedEventException.php:
--------------------------------------------------------------------------------
1 | index = $index;
24 |
25 | parent::__construct(
26 | sprintf('No event defined for index %d.', $index),
27 | 0,
28 | $cause
29 | );
30 | }
31 |
32 | /**
33 | * Get the index.
34 | *
35 | * @return int The index.
36 | */
37 | public function index(): int
38 | {
39 | return $this->index;
40 | }
41 |
42 | /**
43 | * @var int
44 | */
45 | private $index;
46 | }
47 |
--------------------------------------------------------------------------------
/src/Exporter/Exporter.php:
--------------------------------------------------------------------------------
1 | >
72 | */
73 | public $children = [];
74 | }
75 |
--------------------------------------------------------------------------------
/src/Facade/FacadeContainer.php:
--------------------------------------------------------------------------------
1 | initializeContainer(new ExceptionAssertionRecorder());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Facade/Globals.php:
--------------------------------------------------------------------------------
1 | matcher = $matcher;
24 | }
25 |
26 | /**
27 | * Returns `true` if `$value` matches this matcher's criteria.
28 | *
29 | * @param mixed $value The value to check.
30 | *
31 | * @return bool True if the value matches.
32 | */
33 | public function matches($value): bool
34 | {
35 | return (bool) $this->matcher->matches($value);
36 | }
37 |
38 | /**
39 | * Describe this matcher.
40 | *
41 | * @param ?Exporter $exporter The exporter to use.
42 | *
43 | * @return string The description.
44 | */
45 | public function describe(Exporter $exporter = null): string
46 | {
47 | return '<' . strval($this->matcher) . '>';
48 | }
49 |
50 | /**
51 | * Describe this matcher.
52 | *
53 | * @return string The description.
54 | */
55 | public function __toString(): string
56 | {
57 | return '<' . strval($this->matcher) . '>';
58 | }
59 |
60 | /**
61 | * @var ExternalMatcher
62 | */
63 | private $matcher;
64 | }
65 |
--------------------------------------------------------------------------------
/src/Hamcrest/HamcrestMatcherDriver.php:
--------------------------------------------------------------------------------
1 | The matcher class names.
30 | */
31 | public function matcherClassNames(): array
32 | {
33 | return [ExternalMatcher::class];
34 | }
35 |
36 | /**
37 | * Wrap the supplied third party matcher.
38 | *
39 | * @param ExternalMatcher $matcher The matcher to wrap.
40 | *
41 | * @return Matcher The wrapped matcher.
42 | */
43 | public function wrapMatcher(object $matcher): Matcher
44 | {
45 | return new HamcrestMatcher($matcher);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Hook/Exception/FunctionExistsException.php:
--------------------------------------------------------------------------------
1 | functionName = $functionName;
23 |
24 | parent::__construct(
25 | sprintf(
26 | 'Function %s is already defined.',
27 | var_export($functionName, true)
28 | )
29 | );
30 | }
31 |
32 | /**
33 | * Get the function name.
34 | *
35 | * @return string The function name.
36 | */
37 | public function functionName(): string
38 | {
39 | return $this->functionName;
40 | }
41 |
42 | /**
43 | * @var string
44 | */
45 | private $functionName;
46 | }
47 |
--------------------------------------------------------------------------------
/src/Hook/Exception/FunctionHookException.php:
--------------------------------------------------------------------------------
1 | functionName = $functionName;
33 | $this->callback = $callback;
34 | $this->source = $source;
35 | $this->error = $error;
36 |
37 | $lines = explode(PHP_EOL, $source);
38 |
39 | if (null === $error) {
40 | $message = sprintf(
41 | 'Function hook %s generation failed.%sRelevant lines:%%s',
42 | $functionName,
43 | PHP_EOL
44 | );
45 | $errorLineNumber = null;
46 | } else {
47 | /** @var int */
48 | $errorLineNumber = $error['line'];
49 | $startLine = $errorLineNumber - 4;
50 | $contextLineCount = 7;
51 |
52 | if ($startLine < 0) {
53 | $contextLineCount += $startLine;
54 | $startLine = 0;
55 | }
56 |
57 | $lines = array_slice($lines, $startLine, $contextLineCount, true);
58 |
59 | $message = sprintf(
60 | 'Function hook %s generation failed: ' .
61 | '%s in generated code on line %d.%s' .
62 | 'Relevant lines:%%s',
63 | $functionName,
64 | $error['message'],
65 | $errorLineNumber,
66 | PHP_EOL
67 | );
68 | }
69 |
70 | end($lines);
71 | $lineNumber = key($lines);
72 | $padSize = strlen((string) ($lineNumber + 1)) + 4;
73 | $renderedLines = '';
74 |
75 | foreach ($lines as $lineNumber => $line) {
76 | if (null !== $errorLineNumber) {
77 | $highlight = $lineNumber + 1 === $errorLineNumber;
78 | } else {
79 | $highlight = false;
80 | }
81 |
82 | $renderedLines .= sprintf(
83 | '%s%s%s %s',
84 | PHP_EOL,
85 | str_pad(
86 | (string) ($lineNumber + 1),
87 | $padSize,
88 | ' ',
89 | STR_PAD_LEFT
90 | ),
91 | $highlight ? ':' : ' ',
92 | $line
93 | );
94 | }
95 |
96 | parent::__construct(sprintf($message, $renderedLines), 0, $cause);
97 | }
98 |
99 | /**
100 | * Get the function name.
101 | *
102 | * @return string The function name.
103 | */
104 | public function functionName(): string
105 | {
106 | return $this->functionName;
107 | }
108 |
109 | /**
110 | * Get the callback.
111 | *
112 | * @return callable The callback.
113 | */
114 | public function callback(): callable
115 | {
116 | return $this->callback;
117 | }
118 |
119 | /**
120 | * Get the generated source code.
121 | *
122 | * @return string The generated source code.
123 | */
124 | public function source(): string
125 | {
126 | return $this->source;
127 | }
128 |
129 | /**
130 | * Get the error details.
131 | *
132 | * @return ?array The error details.
133 | */
134 | public function error(): ?array
135 | {
136 | return $this->error;
137 | }
138 |
139 | /**
140 | * @var string
141 | */
142 | private $functionName;
143 |
144 | /**
145 | * @var callable
146 | */
147 | private $callback;
148 |
149 | /**
150 | * @var string
151 | */
152 | private $source;
153 |
154 | /**
155 | * @var ?array
156 | */
157 | private $error;
158 | }
159 |
--------------------------------------------------------------------------------
/src/Hook/Exception/FunctionSignatureMismatchException.php:
--------------------------------------------------------------------------------
1 | functionName = $functionName;
23 |
24 | parent::__construct(
25 | sprintf(
26 | 'Function %s has a different signature to the supplied ' .
27 | 'callback.',
28 | var_export($functionName, true)
29 | )
30 | );
31 | }
32 |
33 | /**
34 | * Get the function name.
35 | *
36 | * @return string The function name.
37 | */
38 | public function functionName(): string
39 | {
40 | return $this->functionName;
41 | }
42 |
43 | /**
44 | * @var string
45 | */
46 | private $functionName;
47 | }
48 |
--------------------------------------------------------------------------------
/src/Hook/FunctionHookGenerator.php:
--------------------------------------------------------------------------------
1 | >,string} $signature The function signature.
18 | *
19 | * @return string The source code.
20 | */
21 | public function generateHook(
22 | string $name,
23 | string $namespace,
24 | array $signature
25 | ): string {
26 | $arguments = self::VAR_PREFIX . 'arguments';
27 | $argumentCount = self::VAR_PREFIX . 'argumentCount';
28 | $i = self::VAR_PREFIX . 'i';
29 | $nameVar = self::VAR_PREFIX . 'name';
30 | $callback = self::VAR_PREFIX . 'callback';
31 |
32 | $source = "namespace $namespace;\n\nfunction $name";
33 | list($parameters) = $signature;
34 | $parameterCount = count($parameters);
35 |
36 | if ($parameterCount > 0) {
37 | $isFirst = true;
38 |
39 | foreach ($parameters as $parameterName => $parameter) {
40 | if ($isFirst) {
41 | $isFirst = false;
42 | $source .= "(\n ";
43 | } else {
44 | $source .= ",\n ";
45 | }
46 |
47 | $source .= $parameter[0] .
48 | $parameter[1] .
49 | $parameter[2] .
50 | '$' . $parameterName .
51 | $parameter[3];
52 | }
53 |
54 | $source .= "\n) {\n";
55 | } else {
56 | $source .= "()\n{\n";
57 | }
58 |
59 | $variadicIndex = -1;
60 | $variadicReference = '';
61 | $variadicName = '';
62 |
63 | if ($parameterCount > 0) {
64 | $argumentPacking = "\n";
65 | $index = -1;
66 |
67 | foreach ($parameters as $parameterName => $parameter) {
68 | if ($parameter[2]) {
69 | --$parameterCount;
70 |
71 | $variadicIndex = ++$index;
72 | $variadicReference = $parameter[1];
73 | $variadicName = $parameterName;
74 | } else {
75 | $argumentPacking .=
76 | "\n if ($argumentCount > " .
77 | ++$index .
78 | ") {\n {$arguments}[] = " .
79 | $parameter[1] .
80 | '$' . $parameterName .
81 | ";\n }";
82 | }
83 | }
84 | } else {
85 | $argumentPacking = '';
86 | }
87 |
88 | $source .=
89 | " $argumentCount = \\func_num_args();\n" .
90 | " $arguments = [];" .
91 | $argumentPacking .
92 | "\n\n for ($i = " .
93 | $parameterCount .
94 | "; $i < $argumentCount; ++$i) {\n";
95 |
96 | if ($variadicIndex > -1) {
97 | $source .=
98 | " {$arguments}[] = $variadicReference\$" .
99 | "{$variadicName}[$i - $variadicIndex];\n" .
100 | ' }';
101 | } else {
102 | $source .=
103 | " {$arguments}[] = \\func_get_arg($i);\n" .
104 | ' }';
105 | }
106 |
107 | $ret = 'ret' . 'urn';
108 |
109 | $renderedName = var_export(strtolower($namespace . '\\' . $name), true);
110 | $source .=
111 | "\n\n $nameVar = $renderedName;\n\n if (" .
112 | "\n !isset(\n " .
113 | '\Eloquent\Phony\Hook\FunctionHookManager::$hooks' . "[$nameVar]" .
114 | "['callback']\n )\n ) {\n " .
115 | "$ret \\$name(...$arguments);" .
116 | "\n }\n\n $callback =\n " .
117 | '\Eloquent\Phony\Hook\FunctionHookManager::$hooks' .
118 | "[$nameVar]['callback'];\n\n" .
119 | " if ($callback instanceof " .
120 | "\Eloquent\Phony\Invocation\Invocable) {\n" .
121 | " $ret {$callback}->invokeWith($arguments);\n" .
122 | " }\n\n " .
123 | "$ret $callback(...$arguments);\n}\n";
124 |
125 | // @codeCoverageIgnoreStart
126 | if ("\n" !== PHP_EOL) {
127 | $source = str_replace("\n", PHP_EOL, $source);
128 | }
129 | // @codeCoverageIgnoreEnd
130 |
131 | return $source;
132 | }
133 |
134 | const VAR_PREFIX = "$\u{a4}";
135 | }
136 |
--------------------------------------------------------------------------------
/src/Invocation/Invocable.php:
--------------------------------------------------------------------------------
1 | $arguments The arguments.
21 | *
22 | * @return mixed The result of invocation.
23 | * @throws Throwable If an error occurs.
24 | */
25 | public function invokeWith($arguments = []);
26 |
27 | /**
28 | * Invoke this object.
29 | *
30 | * @param mixed ...$arguments The arguments.
31 | *
32 | * @return mixed The result of invocation.
33 | * @throws Throwable If an error occurs.
34 | */
35 | public function invoke(...$arguments);
36 |
37 | /**
38 | * Invoke this object.
39 | *
40 | * @param mixed ...$arguments The arguments.
41 | *
42 | * @return mixed The result of invocation.
43 | * @throws Throwable If an error occurs.
44 | */
45 | public function __invoke(...$arguments);
46 | }
47 |
--------------------------------------------------------------------------------
/src/Invocation/InvocableInspector.php:
--------------------------------------------------------------------------------
1 | method();
33 | }
34 |
35 | $callback = $callback->callback();
36 | }
37 |
38 | if (is_array($callback)) {
39 | return new ReflectionMethod($callback[0], $callback[1]);
40 | }
41 |
42 | if (is_string($callback) && false !== strpos($callback, '::')) {
43 | list($className, $methodName) = explode('::', $callback);
44 |
45 | return new ReflectionMethod($className, $methodName);
46 | }
47 |
48 | if (is_object($callback) && !$callback instanceof Closure) {
49 | return new ReflectionMethod($callback, '__invoke');
50 | }
51 |
52 | /** @var string $callback */
53 |
54 | return new ReflectionFunction($callback);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Invocation/Invoker.php:
--------------------------------------------------------------------------------
1 | invokeWith($arguments);
28 | }
29 |
30 | $arguments = $arguments->all();
31 |
32 | return $callback(...$arguments);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Invocation/WrappedInvocable.php:
--------------------------------------------------------------------------------
1 | isAnonymous;
22 | }
23 |
24 | /**
25 | * Get the callback.
26 | *
27 | * @return ?callable The callback.
28 | */
29 | public function callback(): ?callable
30 | {
31 | return $this->callback;
32 | }
33 |
34 | /**
35 | * Set the label.
36 | *
37 | * @param string $label The label.
38 | *
39 | * @return $this This invocable.
40 | */
41 | public function setLabel(string $label): WrappedInvocable
42 | {
43 | $this->label = $label;
44 |
45 | return $this;
46 | }
47 |
48 | /**
49 | * Get the label.
50 | *
51 | * @return string The label.
52 | */
53 | public function label(): string
54 | {
55 | return $this->label;
56 | }
57 |
58 | /**
59 | * Invoke this object.
60 | *
61 | * Does not support named arguments.
62 | *
63 | * @param mixed ...$arguments The arguments.
64 | *
65 | * @return mixed The result of invocation.
66 | * @throws Throwable If an error occurs.
67 | */
68 | public function invoke(...$arguments)
69 | {
70 | /** @var array $arguments */
71 |
72 | return $this->invokeWith($arguments);
73 | }
74 |
75 | /**
76 | * Invoke this object.
77 | *
78 | * Does not support named arguments.
79 | *
80 | * @param mixed ...$arguments The arguments.
81 | *
82 | * @return mixed The result of invocation.
83 | * @throws Throwable If an error occurs.
84 | */
85 | public function __invoke(...$arguments)
86 | {
87 | /** @var array $arguments */
88 |
89 | return $this->invokeWith($arguments);
90 | }
91 |
92 | /**
93 | * Invoke this object.
94 | *
95 | * This method supports reference parameters.
96 | *
97 | * @param Arguments|array $arguments The arguments.
98 | *
99 | * @return mixed The result of invocation.
100 | * @throws Throwable If an error occurs.
101 | */
102 | abstract public function invokeWith($arguments = []);
103 |
104 | /**
105 | * @var ?callable
106 | */
107 | protected $callback;
108 |
109 | /**
110 | * @var bool
111 | */
112 | protected $isAnonymous = false;
113 |
114 | /**
115 | * @var string
116 | */
117 | protected $label = '';
118 | }
119 |
--------------------------------------------------------------------------------
/src/Matcher/AnyMatcher.php:
--------------------------------------------------------------------------------
1 | ';
36 | }
37 |
38 | /**
39 | * Describe this matcher.
40 | *
41 | * @return string The description.
42 | */
43 | public function __toString(): string
44 | {
45 | return '';
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Matcher/Exception/UndefinedTypeException.php:
--------------------------------------------------------------------------------
1 | type = $type;
22 |
23 | parent::__construct(
24 | sprintf('Undefined type %s.', var_export($type, true))
25 | );
26 | }
27 |
28 | /**
29 | * Get the type.
30 | *
31 | * @return string The type.
32 | */
33 | public function type(): string
34 | {
35 | return $this->type;
36 | }
37 |
38 | /**
39 | * @var string
40 | */
41 | private $type;
42 | }
43 |
--------------------------------------------------------------------------------
/src/Matcher/InstanceOfMatcher.php:
--------------------------------------------------------------------------------
1 | type = $type;
24 |
25 | $atoms = explode('\\', $type);
26 |
27 | /** @var string */
28 | $shortType = array_pop($atoms);
29 | $this->shortType = $shortType;
30 | }
31 |
32 | /**
33 | * Get the type.
34 | *
35 | * @return string The type.
36 | */
37 | public function type(): string
38 | {
39 | return $this->type;
40 | }
41 |
42 | /**
43 | * Returns `true` if `$value` matches this matcher's criteria.
44 | *
45 | * @param mixed $value The value to check.
46 | *
47 | * @return bool True if the value matches.
48 | */
49 | public function matches($value): bool
50 | {
51 | if (!interface_exists($this->type) && !class_exists($this->type)) {
52 | throw new UndefinedTypeException($this->type);
53 | }
54 |
55 | return $value instanceof $this->type;
56 | }
57 |
58 | /**
59 | * Describe this matcher.
60 | *
61 | * @param ?Exporter $exporter The exporter to use.
62 | *
63 | * @return string The description.
64 | */
65 | public function describe(Exporter $exporter = null): string
66 | {
67 | return 'shortType . '>';
68 | }
69 |
70 | /**
71 | * Describe this matcher.
72 | *
73 | * @return string The description.
74 | */
75 | public function __toString(): string
76 | {
77 | return 'shortType . '>';
78 | }
79 |
80 | /**
81 | * @var string
82 | */
83 | private $type;
84 |
85 | /**
86 | * @var string
87 | */
88 | private $shortType;
89 | }
90 |
--------------------------------------------------------------------------------
/src/Matcher/Matcher.php:
--------------------------------------------------------------------------------
1 | The matcher class names.
23 | */
24 | public function matcherClassNames(): array;
25 |
26 | /**
27 | * Wrap the supplied third party matcher.
28 | *
29 | * @param object $matcher The matcher to wrap.
30 | *
31 | * @return Matcher The wrapped matcher.
32 | */
33 | public function wrapMatcher(object $matcher): Matcher;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Matcher/MatcherResult.php:
--------------------------------------------------------------------------------
1 | $matcherMatches The matcher results.
17 | * @param array $argumentMatches The argument results.
18 | */
19 | public function __construct(
20 | bool $isMatch,
21 | array $matcherMatches,
22 | array $argumentMatches
23 | ) {
24 | $this->isMatch = $isMatch;
25 | $this->matcherMatches = $matcherMatches;
26 | $this->argumentMatches = $argumentMatches;
27 | }
28 |
29 | /**
30 | * @var bool
31 | */
32 | public $isMatch;
33 |
34 | /**
35 | * @var array
36 | */
37 | public $matcherMatches;
38 |
39 | /**
40 | * @var array
41 | */
42 | public $argumentMatches;
43 | }
44 |
--------------------------------------------------------------------------------
/src/Matcher/WildcardMatcher.php:
--------------------------------------------------------------------------------
1 | matcher = $matcher;
29 | $this->minimumArguments = $minimumArguments;
30 | $this->maximumArguments = $maximumArguments;
31 | }
32 |
33 | /**
34 | * Get the matcher to use for each argument.
35 | *
36 | * @return Matcher The matcher.
37 | */
38 | public function matcher(): Matcher
39 | {
40 | return $this->matcher;
41 | }
42 |
43 | /**
44 | * Get the minimum number of arguments to match.
45 | *
46 | * @return int The minimum number of arguments.
47 | */
48 | public function minimumArguments(): int
49 | {
50 | return $this->minimumArguments;
51 | }
52 |
53 | /**
54 | * Get the maximum number of arguments to match.
55 | *
56 | * @return int The maximum number of arguments.
57 | */
58 | public function maximumArguments(): int
59 | {
60 | return $this->maximumArguments;
61 | }
62 |
63 | /**
64 | * Describe this matcher.
65 | *
66 | * @param ?Exporter $exporter The exporter to use.
67 | *
68 | * @return string The description.
69 | */
70 | public function describe(Exporter $exporter = null): string
71 | {
72 | $matcherDescription = $this->matcher->describe($exporter);
73 |
74 | if (0 === $this->minimumArguments) {
75 | if ($this->maximumArguments < 0) {
76 | return sprintf('%s*', $matcherDescription);
77 | } else {
78 | return sprintf(
79 | '%s{,%d}',
80 | $matcherDescription,
81 | $this->maximumArguments
82 | );
83 | }
84 | } elseif ($this->maximumArguments < 0) {
85 | return sprintf(
86 | '%s{%d,}',
87 | $matcherDescription,
88 | $this->minimumArguments
89 | );
90 | } elseif ($this->minimumArguments === $this->maximumArguments) {
91 | return sprintf(
92 | '%s{%d}',
93 | $matcherDescription,
94 | $this->minimumArguments
95 | );
96 | }
97 |
98 | return sprintf(
99 | '%s{%d,%d}',
100 | $matcherDescription,
101 | $this->minimumArguments,
102 | $this->maximumArguments
103 | );
104 | }
105 |
106 | /**
107 | * Describe this matcher.
108 | *
109 | * @return string The description.
110 | */
111 | public function __toString(): string
112 | {
113 | return $this->describe();
114 | }
115 |
116 | /**
117 | * Always returns false.
118 | *
119 | * @param mixed $value The value to check.
120 | *
121 | * @return false For all values.
122 | */
123 | public function matches($value): bool
124 | {
125 | return false;
126 | }
127 |
128 | /**
129 | * @var Matcher
130 | */
131 | private $matcher;
132 |
133 | /**
134 | * @var int
135 | */
136 | private $minimumArguments;
137 |
138 | /**
139 | * @var int
140 | */
141 | private $maximumArguments;
142 | }
143 |
--------------------------------------------------------------------------------
/src/Mock/Builder/Method/CustomMethodDefinition.php:
--------------------------------------------------------------------------------
1 | isStatic = $isStatic;
29 | $this->name = $name;
30 | $this->callback = $callback;
31 | $this->method = $method;
32 | }
33 |
34 | /**
35 | * Returns true if this method is callable.
36 | *
37 | * @return bool True if this method is callable.
38 | */
39 | public function isCallable(): bool
40 | {
41 | return true;
42 | }
43 |
44 | /**
45 | * Returns true if this method is static.
46 | *
47 | * @return bool True if this method is static.
48 | */
49 | public function isStatic(): bool
50 | {
51 | return $this->isStatic;
52 | }
53 |
54 | /**
55 | * Returns true if this method is custom.
56 | *
57 | * @return bool True if this method is custom.
58 | */
59 | public function isCustom(): bool
60 | {
61 | return true;
62 | }
63 |
64 | /**
65 | * Get the access level.
66 | *
67 | * @return string The access level.
68 | */
69 | public function accessLevel(): string
70 | {
71 | return 'public';
72 | }
73 |
74 | /**
75 | * Get the name.
76 | *
77 | * @return string The name.
78 | */
79 | public function name(): string
80 | {
81 | return $this->name;
82 | }
83 |
84 | /**
85 | * Get the method.
86 | *
87 | * @return ReflectionFunctionAbstract The method.
88 | */
89 | public function method(): ReflectionFunctionAbstract
90 | {
91 | return $this->method;
92 | }
93 |
94 | /**
95 | * Get the callback.
96 | *
97 | * @return callable The callback, or null if this is a real method.
98 | */
99 | public function callback(): ?callable
100 | {
101 | return $this->callback;
102 | }
103 |
104 | /**
105 | * @var bool
106 | */
107 | private $isStatic;
108 |
109 | /**
110 | * @var string
111 | */
112 | private $name;
113 |
114 | /**
115 | * @var callable
116 | */
117 | private $callback;
118 |
119 | /**
120 | * @var ReflectionFunctionAbstract
121 | */
122 | private $method;
123 | }
124 |
--------------------------------------------------------------------------------
/src/Mock/Builder/Method/MethodDefinition.php:
--------------------------------------------------------------------------------
1 | method = $method;
24 | $this->name = $name;
25 | $this->isCallable = !$this->method->isAbstract();
26 | $this->isStatic = $this->method->isStatic();
27 |
28 | if ($this->method->isPublic()) {
29 | $this->accessLevel = 'public';
30 | } else {
31 | $this->accessLevel = 'protected';
32 | }
33 | }
34 |
35 | /**
36 | * Returns true if this method is callable.
37 | *
38 | * @return bool True if this method is callable.
39 | */
40 | public function isCallable(): bool
41 | {
42 | return $this->isCallable;
43 | }
44 |
45 | /**
46 | * Returns true if this method is static.
47 | *
48 | * @return bool True if this method is static.
49 | */
50 | public function isStatic(): bool
51 | {
52 | return $this->isStatic;
53 | }
54 |
55 | /**
56 | * Returns true if this method is custom.
57 | *
58 | * @return bool True if this method is custom.
59 | */
60 | public function isCustom(): bool
61 | {
62 | return false;
63 | }
64 |
65 | /**
66 | * Get the access level.
67 | *
68 | * @return string The access level.
69 | */
70 | public function accessLevel(): string
71 | {
72 | return $this->accessLevel;
73 | }
74 |
75 | /**
76 | * Get the name.
77 | *
78 | * @return string The name.
79 | */
80 | public function name(): string
81 | {
82 | return $this->name;
83 | }
84 |
85 | /**
86 | * Get the method.
87 | *
88 | * @return ReflectionMethod The method.
89 | */
90 | public function method(): ReflectionFunctionAbstract
91 | {
92 | return $this->method;
93 | }
94 |
95 | /**
96 | * Get the callback.
97 | *
98 | * @return ?callable The callback, or null if this is a real method.
99 | */
100 | public function callback(): ?callable
101 | {
102 | return null;
103 | }
104 |
105 | /**
106 | * @var ReflectionMethod
107 | */
108 | private $method;
109 |
110 | /**
111 | * @var string
112 | */
113 | private $name;
114 |
115 | /**
116 | * @var bool
117 | */
118 | private $isCallable;
119 |
120 | /**
121 | * @var bool
122 | */
123 | private $isStatic;
124 |
125 | /**
126 | * @var string
127 | */
128 | private $accessLevel;
129 | }
130 |
--------------------------------------------------------------------------------
/src/Mock/Builder/Method/TraitMethodDefinition.php:
--------------------------------------------------------------------------------
1 | mockGenerator = $mockGenerator;
35 | $this->mockFactory = $mockFactory;
36 | $this->handleFactory = $handleFactory;
37 | $this->invocableInspector = $invocableInspector;
38 | $this->featureDetector = $featureDetector;
39 | }
40 |
41 | /**
42 | * Create a new mock builder.
43 | *
44 | * Each value in `$types` can be either a class name, or an ad hoc mock
45 | * definition. If only a single type is being mocked, the class name or
46 | * definition can be passed without being wrapped in an array.
47 | *
48 | * @param mixed $types The types to mock.
49 | *
50 | * @return MockBuilder The mock builder.
51 | */
52 | public function create($types = []): MockBuilder
53 | {
54 | return new MockBuilder(
55 | $types,
56 | $this->mockGenerator,
57 | $this->mockFactory,
58 | $this->handleFactory,
59 | $this->invocableInspector,
60 | $this->featureDetector
61 | );
62 | }
63 |
64 | /**
65 | * @var MockGenerator
66 | */
67 | private $mockGenerator;
68 |
69 | /**
70 | * @var MockFactory
71 | */
72 | private $mockFactory;
73 |
74 | /**
75 | * @var HandleFactory
76 | */
77 | private $handleFactory;
78 |
79 | /**
80 | * @var InvocableInspector
81 | */
82 | private $invocableInspector;
83 |
84 | /**
85 | * @var FeatureDetector
86 | */
87 | private $featureDetector;
88 | }
89 |
--------------------------------------------------------------------------------
/src/Mock/Exception/AnonymousClassException.php:
--------------------------------------------------------------------------------
1 | className = $className;
23 |
24 | parent::__construct(
25 | sprintf(
26 | 'Class %s is already defined.',
27 | var_export($className, true)
28 | )
29 | );
30 | }
31 |
32 | /**
33 | * Get the class name.
34 | *
35 | * @return string The class name.
36 | */
37 | public function className(): string
38 | {
39 | return $this->className;
40 | }
41 |
42 | /**
43 | * @var string
44 | */
45 | private $className;
46 | }
47 |
--------------------------------------------------------------------------------
/src/Mock/Exception/FinalClassException.php:
--------------------------------------------------------------------------------
1 | className = $className;
23 |
24 | parent::__construct(
25 | sprintf(
26 | 'Unable to extend final class %s.',
27 | var_export($className, true)
28 | )
29 | );
30 | }
31 |
32 | /**
33 | * Get the class name.
34 | *
35 | * @return string The class name.
36 | */
37 | public function className(): string
38 | {
39 | return $this->className;
40 | }
41 |
42 | /**
43 | * @var string
44 | */
45 | private $className;
46 | }
47 |
--------------------------------------------------------------------------------
/src/Mock/Exception/FinalMethodStubException.php:
--------------------------------------------------------------------------------
1 | className = $className;
24 | $this->name = $name;
25 |
26 | parent::__construct(
27 | sprintf(
28 | 'The method %s::%s() cannot be stubbed because it is final.',
29 | $className,
30 | $name
31 | )
32 | );
33 | }
34 |
35 | /**
36 | * Get the class name.
37 | *
38 | * @return string The class name.
39 | */
40 | public function className(): string
41 | {
42 | return $this->className;
43 | }
44 |
45 | /**
46 | * Get the method name.
47 | *
48 | * @return string The method name.
49 | */
50 | public function name(): string
51 | {
52 | return $this->name;
53 | }
54 |
55 | /**
56 | * @var string
57 | */
58 | private $className;
59 |
60 | /**
61 | * @var string
62 | */
63 | private $name;
64 | }
65 |
--------------------------------------------------------------------------------
/src/Mock/Exception/FinalizedMockException.php:
--------------------------------------------------------------------------------
1 | className = $className;
23 |
24 | parent::__construct(
25 | sprintf('Invalid class name %s.', var_export($className, true))
26 | );
27 | }
28 |
29 | /**
30 | * Get the class name.
31 | *
32 | * @return mixed The class name.
33 | */
34 | public function className()
35 | {
36 | return $this->className;
37 | }
38 |
39 | /**
40 | * @var mixed
41 | */
42 | private $className;
43 | }
44 |
--------------------------------------------------------------------------------
/src/Mock/Exception/InvalidDefinitionException.php:
--------------------------------------------------------------------------------
1 | name = $name;
24 | $this->value = $value;
25 |
26 | parent::__construct(
27 | sprintf(
28 | 'Invalid mock definition %s: (%s).',
29 | var_export($name, true),
30 | gettype($value)
31 | )
32 | );
33 | }
34 |
35 | /**
36 | * Get the name.
37 | *
38 | * @return mixed The name.
39 | */
40 | public function name()
41 | {
42 | return $this->name;
43 | }
44 |
45 | /**
46 | * Get the value.
47 | *
48 | * @return mixed The value.
49 | */
50 | public function value()
51 | {
52 | return $this->value;
53 | }
54 |
55 | /**
56 | * @var mixed
57 | */
58 | private $name;
59 |
60 | /**
61 | * @var mixed
62 | */
63 | private $value;
64 | }
65 |
--------------------------------------------------------------------------------
/src/Mock/Exception/InvalidMockClassException.php:
--------------------------------------------------------------------------------
1 | value = $value;
23 |
24 | parent::__construct(
25 | sprintf(
26 | 'Value of type %s is not a mock class.',
27 | var_export(gettype($value), true)
28 | )
29 | );
30 | }
31 |
32 | /**
33 | * Get the value.
34 | *
35 | * @return mixed The value.
36 | */
37 | public function value()
38 | {
39 | return $this->value;
40 | }
41 |
42 | /**
43 | * @var mixed
44 | */
45 | private $value;
46 | }
47 |
--------------------------------------------------------------------------------
/src/Mock/Exception/InvalidMockException.php:
--------------------------------------------------------------------------------
1 | value = $value;
23 |
24 | if (is_object($value)) {
25 | $message = sprintf(
26 | 'Object of type %s is not a mock.',
27 | var_export(get_class($value), true)
28 | );
29 | } else {
30 | $message = sprintf(
31 | 'Value of type %s is not a mock.',
32 | var_export(gettype($value), true)
33 | );
34 | }
35 |
36 | parent::__construct($message);
37 | }
38 |
39 | /**
40 | * Get the value.
41 | *
42 | * @return mixed The value.
43 | */
44 | public function value()
45 | {
46 | return $this->value;
47 | }
48 |
49 | /**
50 | * @var mixed
51 | */
52 | private $value;
53 | }
54 |
--------------------------------------------------------------------------------
/src/Mock/Exception/InvalidTypeException.php:
--------------------------------------------------------------------------------
1 | type = $type;
25 |
26 | if (is_string($type)) {
27 | $message = sprintf('Undefined type %s.', var_export($type, true));
28 | } else {
29 | $message = sprintf(
30 | 'Unable to add type of type %s.',
31 | var_export(gettype($type), true)
32 | );
33 | }
34 |
35 | parent::__construct($message, 0, $cause);
36 | }
37 |
38 | /**
39 | * Get the type.
40 | *
41 | * @return mixed The type.
42 | */
43 | public function type()
44 | {
45 | return $this->type;
46 | }
47 |
48 | /**
49 | * @var mixed
50 | */
51 | private $type;
52 | }
53 |
--------------------------------------------------------------------------------
/src/Mock/Exception/MockException.php:
--------------------------------------------------------------------------------
1 | definition = $definition;
34 | $this->source = $source;
35 | $this->error = $error;
36 |
37 | $lines = explode(PHP_EOL, $source);
38 |
39 | if (null === $error) {
40 | $message = sprintf(
41 | 'Mock class %s generation failed.%sRelevant lines:%%s',
42 | $className,
43 | PHP_EOL
44 | );
45 | $errorLineNumber = null;
46 | } else {
47 | /** @var int */
48 | $errorLineNumber = $error['line'];
49 | $startLine = $errorLineNumber - 4;
50 | $contextLineCount = 7;
51 |
52 | if ($startLine < 0) {
53 | $contextLineCount += $startLine;
54 | $startLine = 0;
55 | }
56 |
57 | $lines = array_slice($lines, $startLine, $contextLineCount, true);
58 |
59 | $message = sprintf(
60 | 'Mock class %s generation failed: ' .
61 | '%s in generated code on line %d.%s' .
62 | 'Relevant lines:%%s',
63 | $className,
64 | $error['message'],
65 | $errorLineNumber,
66 | PHP_EOL
67 | );
68 | }
69 |
70 | end($lines);
71 | $lineNumber = key($lines);
72 | $padSize = strlen((string) ($lineNumber + 1)) + 4;
73 | $renderedLines = '';
74 |
75 | foreach ($lines as $lineNumber => $line) {
76 | if (null !== $errorLineNumber) {
77 | $highlight = $lineNumber + 1 === $errorLineNumber;
78 | } else {
79 | $highlight = false;
80 | }
81 |
82 | $renderedLines .= sprintf(
83 | '%s%s%s %s',
84 | PHP_EOL,
85 | str_pad(
86 | (string) ($lineNumber + 1),
87 | $padSize,
88 | ' ',
89 | STR_PAD_LEFT
90 | ),
91 | $highlight ? ':' : ' ',
92 | $line
93 | );
94 | }
95 |
96 | parent::__construct(sprintf($message, $renderedLines), 0, $cause);
97 | }
98 |
99 | /**
100 | * Get the definition.
101 | *
102 | * @return MockDefinition The definition.
103 | */
104 | public function definition(): MockDefinition
105 | {
106 | return $this->definition;
107 | }
108 |
109 | /**
110 | * Get the generated source code.
111 | *
112 | * @return string The generated source code.
113 | */
114 | public function source(): string
115 | {
116 | return $this->source;
117 | }
118 |
119 | /**
120 | * Get the error details.
121 | *
122 | * @return ?array The error details.
123 | */
124 | public function error(): ?array
125 | {
126 | return $this->error;
127 | }
128 |
129 | /**
130 | * @var MockDefinition
131 | */
132 | private $definition;
133 |
134 | /**
135 | * @var string
136 | */
137 | private $source;
138 |
139 | /**
140 | * @var ?array
141 | */
142 | private $error;
143 | }
144 |
--------------------------------------------------------------------------------
/src/Mock/Exception/MultipleInheritanceException.php:
--------------------------------------------------------------------------------
1 | $classNames The class names.
19 | */
20 | public function __construct(array $classNames)
21 | {
22 | $this->classNames = $classNames;
23 |
24 | parent::__construct(
25 | sprintf(
26 | 'Unable to extend %s simultaneously.',
27 | implode(
28 | ' and ',
29 | array_map(
30 | function ($className) {
31 | return var_export($className, true);
32 | },
33 | $classNames
34 | )
35 | )
36 | )
37 | );
38 | }
39 |
40 | /**
41 | * Get the class names.
42 | *
43 | * @return array The class names.
44 | */
45 | public function classNames(): array
46 | {
47 | return $this->classNames;
48 | }
49 |
50 | /**
51 | * @var array
52 | */
53 | private $classNames;
54 | }
55 |
--------------------------------------------------------------------------------
/src/Mock/Exception/NonMockClassException.php:
--------------------------------------------------------------------------------
1 | className = $className;
25 |
26 | parent::__construct(
27 | sprintf(
28 | 'The class %s is not a mock class.',
29 | var_export($className, true)
30 | ),
31 | 0,
32 | $cause
33 | );
34 | }
35 |
36 | /**
37 | * Get the class name.
38 | *
39 | * @return string The class name.
40 | */
41 | public function className(): string
42 | {
43 | return $this->className;
44 | }
45 |
46 | /**
47 | * @var string
48 | */
49 | private $className;
50 | }
51 |
--------------------------------------------------------------------------------
/src/Mock/Exception/UndefinedMethodStubException.php:
--------------------------------------------------------------------------------
1 | className = $className;
24 | $this->name = $name;
25 |
26 | parent::__construct(
27 | sprintf(
28 | 'The requested method stub %s::%s() does not exist.',
29 | $className,
30 | $name
31 | )
32 | );
33 | }
34 |
35 | /**
36 | * Get the class name.
37 | *
38 | * @return string The class name.
39 | */
40 | public function className(): string
41 | {
42 | return $this->className;
43 | }
44 |
45 | /**
46 | * Get the method name.
47 | *
48 | * @return string The method name.
49 | */
50 | public function name(): string
51 | {
52 | return $this->name;
53 | }
54 |
55 | /**
56 | * @var string
57 | */
58 | private $className;
59 |
60 | /**
61 | * @var string
62 | */
63 | private $name;
64 | }
65 |
--------------------------------------------------------------------------------
/src/Mock/Handle/Handle.php:
--------------------------------------------------------------------------------
1 | The class.
24 | */
25 | public function class(): ReflectionClass;
26 |
27 | /**
28 | * Get the class name.
29 | *
30 | * @return string The class name.
31 | */
32 | public function className(): string;
33 |
34 | /**
35 | * Turn the mock into a full mock.
36 | *
37 | * @return $this This handle.
38 | */
39 | public function full(): self;
40 |
41 | /**
42 | * Turn the mock into a partial mock.
43 | *
44 | * @return $this This handle.
45 | */
46 | public function partial(): self;
47 |
48 | /**
49 | * Use the supplied object as the implementation for all methods of the
50 | * mock.
51 | *
52 | * This method may help when partial mocking of a particular implementation
53 | * is not possible; as in the case of a final class.
54 | *
55 | * @param object $object The object to use.
56 | *
57 | * @return $this This handle.
58 | */
59 | public function proxy($object): self;
60 |
61 | /**
62 | * Set the callback to use when creating a default answer.
63 | *
64 | * @param callable $defaultAnswerCallback The default answer callback.
65 | *
66 | * @return $this This handle.
67 | */
68 | public function setDefaultAnswerCallback(
69 | callable $defaultAnswerCallback
70 | ): self;
71 |
72 | /**
73 | * Get the default answer callback.
74 | *
75 | * @return callable The default answer callback.
76 | */
77 | public function defaultAnswerCallback(): callable;
78 |
79 | /**
80 | * Get a stub verifier.
81 | *
82 | * @param string $name The method name.
83 | * @param bool $isNewRule True if a new rule should be started.
84 | *
85 | * @return StubVerifier The stub verifier.
86 | * @throws MockException If the stub does not exist.
87 | */
88 | public function stub(string $name, bool $isNewRule = true): StubVerifier;
89 |
90 | /**
91 | * Get a stub verifier.
92 | *
93 | * Using this method will always start a new rule.
94 | *
95 | * @param string $name The method name.
96 | *
97 | * @return StubVerifier The stub verifier.
98 | * @throws MockException If the stub does not exist.
99 | */
100 | public function __get(string $name): StubVerifier;
101 |
102 | /**
103 | * Checks if there was no interaction with the mock.
104 | *
105 | * @return ?EventCollection The result.
106 | */
107 | public function checkNoInteraction(): ?EventCollection;
108 |
109 | /**
110 | * Record an assertion failure unless there was no interaction with the mock.
111 | *
112 | * @return ?EventCollection The result, or null if the assertion recorder does not throw exceptions.
113 | * @throws Throwable If the assertion fails, and the assertion recorder throws exceptions.
114 | */
115 | public function noInteraction(): ?EventCollection;
116 |
117 | /**
118 | * Stop recording calls.
119 | *
120 | * @return $this This handle.
121 | */
122 | public function stopRecording(): self;
123 |
124 | /**
125 | * Start recording calls.
126 | *
127 | * @return $this This handle.
128 | */
129 | public function startRecording(): self;
130 |
131 | /**
132 | * Get a spy.
133 | *
134 | * @param string $name The method name.
135 | *
136 | * @return Spy The spy.
137 | * @throws MockException If the spy does not exist.
138 | */
139 | public function spy(string $name): Spy;
140 |
141 | /**
142 | * Get the handle state.
143 | *
144 | * @return stdClass The state.
145 | */
146 | public function state(): stdClass;
147 | }
148 |
--------------------------------------------------------------------------------
/src/Mock/Handle/StaticHandle.php:
--------------------------------------------------------------------------------
1 | $class The class.
30 | * @param stdClass $state The state.
31 | * @param StubFactory $stubFactory The stub factory to use.
32 | * @param StubVerifierFactory $stubVerifierFactory The stub verifier factory to use.
33 | * @param EmptyValueFactory $emptyValueFactory The empty value factory to use.
34 | * @param AssertionRenderer $assertionRenderer The assertion renderer to use.
35 | * @param AssertionRecorder $assertionRecorder The assertion recorder to use.
36 | * @param Invoker $invoker The invoker to use.
37 | */
38 | public function __construct(
39 | MockDefinition $mockDefinition,
40 | ReflectionClass $class,
41 | stdClass $state,
42 | StubFactory $stubFactory,
43 | StubVerifierFactory $stubVerifierFactory,
44 | EmptyValueFactory $emptyValueFactory,
45 | AssertionRenderer $assertionRenderer,
46 | AssertionRecorder $assertionRecorder,
47 | Invoker $invoker
48 | ) {
49 | if ($class->hasMethod('_callParentStatic')) {
50 | $callParentMethod = $class->getMethod('_callParentStatic');
51 | $callParentMethod->setAccessible(true);
52 | } else {
53 | $callParentMethod = null;
54 | }
55 |
56 | if ($class->hasMethod('_callTraitStatic')) {
57 | $callTraitMethod = $class->getMethod('_callTraitStatic');
58 | $callTraitMethod->setAccessible(true);
59 | } else {
60 | $callTraitMethod = null;
61 | }
62 |
63 | if ($class->hasMethod('_callMagicStatic')) {
64 | $callMagicMethod = $class->getMethod('_callMagicStatic');
65 | $callMagicMethod->setAccessible(true);
66 | } else {
67 | $callMagicMethod = null;
68 | }
69 |
70 | $this->constructHandle(
71 | $mockDefinition,
72 | $class,
73 | $state,
74 | $callParentMethod,
75 | $callTraitMethod,
76 | $callMagicMethod,
77 | null,
78 | $stubFactory,
79 | $stubVerifierFactory,
80 | $emptyValueFactory,
81 | $assertionRenderer,
82 | $assertionRecorder,
83 | $invoker
84 | );
85 | }
86 |
87 | /**
88 | * Use the supplied object as the implementation for all methods of the
89 | * mock.
90 | *
91 | * This method may help when partial mocking of a particular implementation
92 | * is not possible; as in the case of a final class.
93 | *
94 | * @param object $object The object to use.
95 | *
96 | * @return $this This handle.
97 | */
98 | public function proxy($object): Handle
99 | {
100 | $reflector = new ReflectionObject($object);
101 |
102 | foreach ($reflector->getMethods() as $method) {
103 | if (!$method->isStatic() || $method->isPrivate()) {
104 | continue;
105 | }
106 |
107 | $name = $method->getName();
108 |
109 | if ($this->class->hasMethod($name)) {
110 | $method->setAccessible(true);
111 |
112 | $this->stub($name)->doesWith(
113 | function ($arguments) use ($method, $object) {
114 | return $method->invokeArgs($object, $arguments->all());
115 | },
116 | [],
117 | false,
118 | true,
119 | false
120 | );
121 | }
122 | }
123 |
124 | return $this;
125 | }
126 |
127 | /**
128 | * Limits the output displayed when `var_dump` is used.
129 | *
130 | * @return array The contents to export.
131 | */
132 | public function __debugInfo(): array
133 | {
134 | return ['class' => $this->class];
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/Mock/Handle/StaticHandleRegistry.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | public static $handles = [];
16 | }
17 |
--------------------------------------------------------------------------------
/src/Mock/Method/WrappedCustomMethod.php:
--------------------------------------------------------------------------------
1 | customCallback = $customCallback;
35 | $this->invoker = $invoker;
36 |
37 | $this->constructWrappedMethod($method, $handle);
38 | }
39 |
40 | /**
41 | * Get the custom callback.
42 | *
43 | * @return callable The custom callback.
44 | */
45 | public function customCallback(): callable
46 | {
47 | return $this->customCallback;
48 | }
49 |
50 | /**
51 | * Invoke this object.
52 | *
53 | * This method supports reference parameters.
54 | *
55 | * @param Arguments|array $arguments The arguments.
56 | *
57 | * @return mixed The result of invocation.
58 | * @throws Throwable If an error occurs.
59 | */
60 | public function invokeWith($arguments = [])
61 | {
62 | if (!$arguments instanceof Arguments) {
63 | $arguments = new Arguments($arguments);
64 | }
65 |
66 | return $this->invoker->callWith($this->customCallback, $arguments);
67 | }
68 |
69 | /**
70 | * @var callable
71 | */
72 | private $customCallback;
73 |
74 | /**
75 | * @var Invoker
76 | */
77 | private $invoker;
78 | }
79 |
--------------------------------------------------------------------------------
/src/Mock/Method/WrappedMagicMethod.php:
--------------------------------------------------------------------------------
1 | callMagicMethod = $callMagicMethod;
42 | $this->method = $method;
43 | $this->name = $name;
44 | $this->isUncallable = $isUncallable;
45 | $this->handle = $handle;
46 | $this->exception = $exception;
47 | $this->returnValue = $returnValue;
48 |
49 | if ($handle instanceof StaticHandle) {
50 | $this->mock = null;
51 | } elseif ($handle instanceof InstanceHandle) {
52 | $this->mock = $handle->get();
53 | }
54 | }
55 |
56 | /**
57 | * Get the method.
58 | *
59 | * @return ReflectionMethod The method.
60 | */
61 | public function callMagicMethod(): ReflectionMethod
62 | {
63 | return $this->callMagicMethod;
64 | }
65 |
66 | /**
67 | * Returns true if uncallable.
68 | *
69 | * @return bool True if uncallable.
70 | */
71 | public function isUncallable(): bool
72 | {
73 | return $this->isUncallable;
74 | }
75 |
76 | /**
77 | * Invoke this object.
78 | *
79 | * This method supports reference parameters.
80 | *
81 | * @param Arguments|array $arguments The arguments.
82 | *
83 | * @return mixed The result of invocation.
84 | * @throws Throwable If an error occurs.
85 | */
86 | public function invokeWith($arguments = [])
87 | {
88 | if ($this->exception) {
89 | throw $this->exception;
90 | }
91 |
92 | if ($this->isUncallable) {
93 | return $this->returnValue;
94 | }
95 |
96 | if (!$arguments instanceof Arguments) {
97 | $arguments = new Arguments($arguments);
98 | }
99 |
100 | return $this->callMagicMethod
101 | ->invoke($this->mock, $this->name, $arguments);
102 | }
103 |
104 | /**
105 | * @var string
106 | */
107 | private $name;
108 |
109 | /**
110 | * @var ReflectionMethod
111 | */
112 | private $callMagicMethod;
113 |
114 | /**
115 | * @var bool
116 | */
117 | private $isUncallable;
118 |
119 | /**
120 | * @var ?Throwable
121 | */
122 | private $exception;
123 |
124 | /**
125 | * @var mixed
126 | */
127 | private $returnValue;
128 | }
129 |
--------------------------------------------------------------------------------
/src/Mock/Method/WrappedMethod.php:
--------------------------------------------------------------------------------
1 | method;
29 | }
30 |
31 | /**
32 | * Get the name.
33 | *
34 | * @return string The name.
35 | */
36 | public function name(): string
37 | {
38 | return $this->name;
39 | }
40 |
41 | /**
42 | * Get the handle.
43 | *
44 | * @return Handle The handle.
45 | */
46 | public function handle(): Handle
47 | {
48 | return $this->handle;
49 | }
50 |
51 | /**
52 | * Get the mock.
53 | *
54 | * @return ?Mock The mock.
55 | */
56 | public function mock(): ?Mock
57 | {
58 | return $this->mock;
59 | }
60 |
61 | private function constructWrappedMethod(
62 | ReflectionMethod $method,
63 | Handle $handle
64 | ): void {
65 | $this->method = $method;
66 | $this->handle = $handle;
67 | $this->name = $method->getName();
68 |
69 | if ($handle instanceof StaticHandle) {
70 | $this->mock = null;
71 | } elseif ($handle instanceof InstanceHandle) {
72 | $this->mock = $handle->get();
73 | }
74 | }
75 |
76 | /**
77 | * @var ReflectionMethod
78 | */
79 | private $method;
80 |
81 | /**
82 | * @var Handle
83 | */
84 | private $handle;
85 |
86 | /**
87 | * @var ?Mock
88 | */
89 | private $mock;
90 |
91 | /**
92 | * @var string
93 | */
94 | private $name;
95 | }
96 |
--------------------------------------------------------------------------------
/src/Mock/Method/WrappedParentMethod.php:
--------------------------------------------------------------------------------
1 | callParentMethod = $callParentMethod;
32 |
33 | $this->constructWrappedMethod($method, $handle);
34 | }
35 |
36 | /**
37 | * Get the _callParent() method.
38 | *
39 | * @return ReflectionMethod The _callParent() method.
40 | */
41 | public function callParentMethod(): ReflectionMethod
42 | {
43 | return $this->callParentMethod;
44 | }
45 |
46 | /**
47 | * Invoke this object.
48 | *
49 | * This method supports reference parameters.
50 | *
51 | * @param Arguments|array $arguments The arguments.
52 | *
53 | * @return mixed The result of invocation.
54 | * @throws Throwable If an error occurs.
55 | */
56 | public function invokeWith($arguments = [])
57 | {
58 | if (!$arguments instanceof Arguments) {
59 | $arguments = new Arguments($arguments);
60 | }
61 |
62 | return $this->callParentMethod
63 | ->invoke($this->mock, $this->name, $arguments);
64 | }
65 |
66 | /**
67 | * @var ReflectionMethod
68 | */
69 | private $callParentMethod;
70 | }
71 |
--------------------------------------------------------------------------------
/src/Mock/Method/WrappedTraitMethod.php:
--------------------------------------------------------------------------------
1 | callTraitMethod = $callTraitMethod;
34 | $this->traitName = $traitName;
35 |
36 | $this->constructWrappedMethod($method, $handle);
37 | }
38 |
39 | /**
40 | * Get the _callTrait() method.
41 | *
42 | * @return ReflectionMethod The _callTrait() method.
43 | */
44 | public function callTraitMethod(): ReflectionMethod
45 | {
46 | return $this->callTraitMethod;
47 | }
48 |
49 | /**
50 | * Get the trait name.
51 | *
52 | * @return string The trait name.
53 | */
54 | public function traitName(): string
55 | {
56 | return $this->traitName;
57 | }
58 |
59 | /**
60 | * Invoke this object.
61 | *
62 | * This method supports reference parameters.
63 | *
64 | * @param Arguments|array $arguments The arguments.
65 | *
66 | * @return mixed The result of invocation.
67 | * @throws Throwable If an error occurs.
68 | */
69 | public function invokeWith($arguments = [])
70 | {
71 | if (!$arguments instanceof Arguments) {
72 | $arguments = new Arguments($arguments);
73 | }
74 |
75 | return $this->callTraitMethod->invoke(
76 | $this->mock,
77 | $this->traitName,
78 | $this->name,
79 | $arguments
80 | );
81 | }
82 |
83 | /**
84 | * @var ReflectionMethod
85 | */
86 | private $callTraitMethod;
87 |
88 | /**
89 | * @var string
90 | */
91 | private $traitName;
92 | }
93 |
--------------------------------------------------------------------------------
/src/Mock/Method/WrappedUncallableMethod.php:
--------------------------------------------------------------------------------
1 | exception = $exception;
34 | $this->returnValue = $returnValue;
35 |
36 | $this->constructWrappedMethod($method, $handle);
37 | }
38 |
39 | /**
40 | * Invoke this object.
41 | *
42 | * This method supports reference parameters.
43 | *
44 | * @param Arguments|array $arguments The arguments.
45 | *
46 | * @return mixed The result of invocation.
47 | * @throws Throwable If an error occurs.
48 | */
49 | public function invokeWith($arguments = [])
50 | {
51 | if ($this->exception) {
52 | throw $this->exception;
53 | }
54 |
55 | return $this->returnValue;
56 | }
57 |
58 | /**
59 | * @var ?Throwable
60 | */
61 | private $exception;
62 |
63 | /**
64 | * @var mixed
65 | */
66 | private $returnValue;
67 | }
68 |
--------------------------------------------------------------------------------
/src/Mock/Mock.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | public $definitions = [];
18 | }
19 |
--------------------------------------------------------------------------------
/src/Phony.php:
--------------------------------------------------------------------------------
1 | feature = $feature;
22 |
23 | parent::__construct(
24 | sprintf('Undefined feature %s.', var_export($feature, true))
25 | );
26 | }
27 |
28 | /**
29 | * Get the feature.
30 | *
31 | * @return string The feature.
32 | */
33 | public function feature(): string
34 | {
35 | return $this->feature;
36 | }
37 |
38 | /**
39 | * @var string
40 | */
41 | private $feature;
42 | }
43 |
--------------------------------------------------------------------------------
/src/Sequencer/Sequencer.php:
--------------------------------------------------------------------------------
1 | current = $current;
36 | }
37 |
38 | /**
39 | * Reset the sequence number to its initial value.
40 | */
41 | public function reset(): void
42 | {
43 | $this->current = -1;
44 | }
45 |
46 | /**
47 | * Get the sequence number.
48 | *
49 | * @return int The sequence number.
50 | */
51 | public function get(): int
52 | {
53 | return $this->current;
54 | }
55 |
56 | /**
57 | * Increment and return the sequence number.
58 | *
59 | * @return int The sequence number.
60 | */
61 | public function next(): int
62 | {
63 | return ++$this->current;
64 | }
65 |
66 | /**
67 | * @var array
68 | */
69 | private static $instances = [];
70 |
71 | /**
72 | * @var int
73 | */
74 | private $current = -1;
75 | }
76 |
--------------------------------------------------------------------------------
/src/Spy/ArraySpy.php:
--------------------------------------------------------------------------------
1 | $array The array.
20 | * @param CallEventFactory $callEventFactory The call event factory to use.
21 | */
22 | public function __construct(
23 | Call $call,
24 | array $array,
25 | CallEventFactory $callEventFactory
26 | ) {
27 | $this->call = $call;
28 | $this->array = $array;
29 | $this->callEventFactory = $callEventFactory;
30 | $this->isUsed = false;
31 | $this->isConsumed = false;
32 | }
33 |
34 | /**
35 | * Get the original iterable value.
36 | *
37 | * @return iterable The original value.
38 | */
39 | public function iterable(): iterable
40 | {
41 | return $this->array;
42 | }
43 |
44 | /**
45 | * Get the current key.
46 | *
47 | * @return mixed The current key.
48 | */
49 | public function key(): mixed
50 | {
51 | return key($this->array);
52 | }
53 |
54 | /**
55 | * Get the current value.
56 | *
57 | * @return mixed The current value.
58 | */
59 | public function current(): mixed
60 | {
61 | return current($this->array);
62 | }
63 |
64 | /**
65 | * Move the current position to the next element.
66 | */
67 | public function next(): void
68 | {
69 | next($this->array);
70 | }
71 |
72 | /**
73 | * Rewind the iterator.
74 | */
75 | public function rewind(): void
76 | {
77 | reset($this->array);
78 | }
79 |
80 | /**
81 | * Returns true if the current iterator position is valid.
82 | *
83 | * @return bool True if the current iterator position is valid.
84 | */
85 | public function valid(): bool
86 | {
87 | if (!$this->isUsed) {
88 | $this->call
89 | ->addIterableEvent($this->callEventFactory->createUsed());
90 | $this->isUsed = true;
91 | }
92 |
93 | $key = key($this->array);
94 | $isValid = null !== $key;
95 |
96 | if ($this->isConsumed) {
97 | return $isValid;
98 | }
99 |
100 | if ($isValid) {
101 | $this->call->addIterableEvent(
102 | $this->callEventFactory
103 | ->createProduced($key, current($this->array))
104 | );
105 | } else {
106 | $this->call->setEndEvent($this->callEventFactory->createConsumed());
107 | $this->isConsumed = true;
108 | }
109 |
110 | return $isValid;
111 | }
112 |
113 | /**
114 | * Check if a key exists.
115 | *
116 | * @param mixed $key The key.
117 | *
118 | * @return bool True if the key exists.
119 | */
120 | public function offsetExists($key): bool
121 | {
122 | return isset($this->array[$key]);
123 | }
124 |
125 | /**
126 | * Get a value.
127 | *
128 | * @param mixed $key The key.
129 | *
130 | * @return mixed The value.
131 | */
132 | public function offsetGet($key): mixed
133 | {
134 | return $this->array[$key];
135 | }
136 |
137 | /**
138 | * Set a value.
139 | *
140 | * @param mixed $key The key.
141 | * @param mixed $value The value.
142 | */
143 | public function offsetSet($key, $value): void
144 | {
145 | $this->array[$key] = $value;
146 | }
147 |
148 | /**
149 | * Un-set a value.
150 | *
151 | * @param mixed $key The key.
152 | */
153 | public function offsetUnset($key): void
154 | {
155 | unset($this->array[$key]);
156 | }
157 |
158 | /**
159 | * Get the count.
160 | *
161 | * @return int The count.
162 | */
163 | public function count(): int
164 | {
165 | return count($this->array);
166 | }
167 |
168 | /**
169 | * @var Call
170 | */
171 | private $call;
172 |
173 | /**
174 | * @var array
175 | */
176 | private $array;
177 |
178 | /**
179 | * @var CallEventFactory
180 | */
181 | private $callEventFactory;
182 |
183 | /**
184 | * @var bool
185 | */
186 | private $isUsed;
187 |
188 | /**
189 | * @var bool
190 | */
191 | private $isConsumed;
192 | }
193 |
--------------------------------------------------------------------------------
/src/Spy/Exception/NonArrayAccessTraversableException.php:
--------------------------------------------------------------------------------
1 | $traversable The traversable.
19 | */
20 | public function __construct(Traversable $traversable)
21 | {
22 | $this->traversable = $traversable;
23 |
24 | parent::__construct(
25 | sprintf(
26 | 'Unable to use array access on a traversable object of type ' .
27 | '%s, since it does not implement ArrayAccess.',
28 | var_export(get_class($traversable), true)
29 | )
30 | );
31 | }
32 |
33 | /**
34 | * Get the traversable.
35 | *
36 | * @return Traversable The traversable.
37 | */
38 | public function traversable(): Traversable
39 | {
40 | return $this->traversable;
41 | }
42 |
43 | /**
44 | * @var Traversable
45 | */
46 | private $traversable;
47 | }
48 |
--------------------------------------------------------------------------------
/src/Spy/Exception/NonCountableTraversableException.php:
--------------------------------------------------------------------------------
1 | $traversable The traversable.
19 | */
20 | public function __construct(Traversable $traversable)
21 | {
22 | $this->traversable = $traversable;
23 |
24 | parent::__construct(
25 | sprintf(
26 | 'Unable to count a traversable object of type %s, since it ' .
27 | 'does not implement Countable.',
28 | var_export(get_class($traversable), true)
29 | )
30 | );
31 | }
32 |
33 | /**
34 | * Get the traversable.
35 | *
36 | * @return Traversable The traversable.
37 | */
38 | public function traversable(): Traversable
39 | {
40 | return $this->traversable;
41 | }
42 |
43 | /**
44 | * @var Traversable
45 | */
46 | private $traversable;
47 | }
48 |
--------------------------------------------------------------------------------
/src/Spy/GeneratorSpyFactory.php:
--------------------------------------------------------------------------------
1 | callEventFactory = $callEventFactory;
28 | $this->generatorSpyMap = $generatorSpyMap;
29 | }
30 |
31 | /**
32 | * Create a new generator spy.
33 | *
34 | * @param Call $call The call from which the generator originated.
35 | * @param Generator $generator The generator.
36 | *
37 | * @return Generator The newly created generator spy.
38 | */
39 | public function create(Call $call, Generator $generator): Generator
40 | {
41 | $spy = $this->createSpy($call, $generator);
42 | $this->generatorSpyMap->set($spy, $generator);
43 |
44 | return $spy;
45 | }
46 |
47 | /**
48 | * @param Generator $generator
49 | *
50 | * @return Generator
51 | */
52 | private function createSpy(Call $call, Generator $generator): Generator
53 | {
54 | $call->addIterableEvent($this->callEventFactory->createUsed());
55 |
56 | $isFirst = true;
57 | $received = null;
58 | $receivedException = null;
59 |
60 | while (true) {
61 | $thrown = null;
62 |
63 | try {
64 | if (!$isFirst) {
65 | if ($receivedException) {
66 | $generator->throw($receivedException);
67 | } else {
68 | $generator->send($received);
69 | }
70 | }
71 |
72 | if (!$generator->valid()) {
73 | $returnValue = $generator->getReturn();
74 | $call->setEndEvent(
75 | $this->callEventFactory->createReturned($returnValue)
76 | );
77 |
78 | return $returnValue;
79 | }
80 | } catch (Throwable $thrown) {
81 | $call->setEndEvent(
82 | $this->callEventFactory->createThrew($thrown)
83 | );
84 |
85 | throw $thrown;
86 | }
87 |
88 | $key = $generator->key();
89 | $value = $generator->current();
90 | $received = null;
91 | $receivedException = null;
92 |
93 | $call->addIterableEvent(
94 | $this->callEventFactory->createProduced($key, $value)
95 | );
96 |
97 | try {
98 | $received = yield $key => $value;
99 |
100 | $call->addIterableEvent(
101 | $this->callEventFactory->createReceived($received)
102 | );
103 | } catch (Throwable $receivedException) {
104 | $call->addIterableEvent(
105 | $this->callEventFactory
106 | ->createReceivedException($receivedException)
107 | );
108 | }
109 |
110 | $isFirst = false;
111 | unset($value);
112 | }
113 | }
114 |
115 | /**
116 | * @var CallEventFactory
117 | */
118 | private $callEventFactory;
119 |
120 | /**
121 | * @var GeneratorSpyMap
122 | */
123 | private $generatorSpyMap;
124 | }
125 |
--------------------------------------------------------------------------------
/src/Spy/GeneratorSpyMap.php:
--------------------------------------------------------------------------------
1 | mapping = new WeakMap();
21 | }
22 |
23 | /**
24 | * Associate a generator spy with the generator it spies on.
25 | *
26 | * @param Generator $spy The generator spy.
27 | * @param Generator $generator The generator.
28 | */
29 | public function set(Generator $spy, Generator $generator): void
30 | {
31 | $this->mapping->offsetSet($spy, $generator);
32 | }
33 |
34 | /**
35 | * Return the generator being spied on by the supplied generator spy.
36 | *
37 | * @param Generator $spy The generator to check.
38 | *
39 | * @return ?Generator The generator, or null if the supplied generator is not a spy.
40 | */
41 | public function get(Generator $spy): ?Generator
42 | {
43 | if ($this->mapping->offsetExists($spy)) {
44 | return $this->mapping->offsetGet($spy);
45 | }
46 |
47 | return null;
48 | }
49 |
50 | /**
51 | * @var WeakMap,Generator>
52 | */
53 | private $mapping;
54 | }
55 |
--------------------------------------------------------------------------------
/src/Spy/IterableSpy.php:
--------------------------------------------------------------------------------
1 |
15 | * @extends Iterator
16 | */
17 | interface IterableSpy extends ArrayAccess, Countable, Iterator
18 | {
19 | /**
20 | * Get the original iterable value.
21 | *
22 | * @return iterable The original value.
23 | */
24 | public function iterable(): iterable;
25 | }
26 |
--------------------------------------------------------------------------------
/src/Spy/IterableSpyFactory.php:
--------------------------------------------------------------------------------
1 | callEventFactory = $callEventFactory;
25 | }
26 |
27 | /**
28 | * Create a new iterable spy.
29 | *
30 | * @param Call $call The call from which the iterable originated.
31 | * @param mixed $iterable The iterable.
32 | *
33 | * @return IterableSpy The newly created iterable spy.
34 | * @throws InvalidArgumentException If the supplied iterable is invalid.
35 | */
36 | public function create(Call $call, $iterable): IterableSpy
37 | {
38 | if ($iterable instanceof Traversable) {
39 | return new TraversableSpy(
40 | $call,
41 | $iterable,
42 | $this->callEventFactory
43 | );
44 | }
45 |
46 | if (is_array($iterable)) {
47 | return new ArraySpy($call, $iterable, $this->callEventFactory);
48 | }
49 |
50 | if (is_object($iterable)) {
51 | $type = var_export(get_class($iterable), true);
52 | } else {
53 | $type = gettype($iterable);
54 | }
55 |
56 | throw new InvalidArgumentException(
57 | sprintf('Unsupported iterable of type %s.', $type)
58 | );
59 | }
60 |
61 | /**
62 | * @var CallEventFactory
63 | */
64 | private $callEventFactory;
65 | }
66 |
--------------------------------------------------------------------------------
/src/Spy/Spy.php:
--------------------------------------------------------------------------------
1 | $calls The calls.
66 | */
67 | public function setCalls(array $calls): void;
68 |
69 | /**
70 | * Add a call.
71 | *
72 | * @param Call $call The call.
73 | */
74 | public function addCall(Call $call): void;
75 | }
76 |
--------------------------------------------------------------------------------
/src/Spy/SpyFactory.php:
--------------------------------------------------------------------------------
1 | labelSequencer = $labelSequencer;
33 | $this->callFactory = $callFactory;
34 | $this->invoker = $invoker;
35 | $this->generatorSpyFactory = $generatorSpyFactory;
36 | $this->iterableSpyFactory = $iterableSpyFactory;
37 | }
38 |
39 | /**
40 | * Create a new spy.
41 | *
42 | * @param ?callable $callback The callback, or null to create an anonymous spy.
43 | *
44 | * @return Spy The newly created spy.
45 | */
46 | public function create(?callable $callback): Spy
47 | {
48 | return new SpyData(
49 | $callback,
50 | strval($this->labelSequencer->next()),
51 | $this->callFactory,
52 | $this->invoker,
53 | $this->generatorSpyFactory,
54 | $this->iterableSpyFactory
55 | );
56 | }
57 |
58 | /**
59 | * @var Sequencer
60 | */
61 | private $labelSequencer;
62 |
63 | /**
64 | * @var CallFactory
65 | */
66 | private $callFactory;
67 |
68 | /**
69 | * @var Invoker
70 | */
71 | private $invoker;
72 |
73 | /**
74 | * @var GeneratorSpyFactory
75 | */
76 | private $generatorSpyFactory;
77 |
78 | /**
79 | * @var IterableSpyFactory
80 | */
81 | private $iterableSpyFactory;
82 | }
83 |
--------------------------------------------------------------------------------
/src/Stub/Answer/Answer.php:
--------------------------------------------------------------------------------
1 | $secondaryRequests The secondary requests.
17 | */
18 | public function __construct(
19 | CallRequest $primaryRequest,
20 | array $secondaryRequests
21 | ) {
22 | $this->primaryRequest = $primaryRequest;
23 | $this->secondaryRequests = $secondaryRequests;
24 | }
25 |
26 | /**
27 | * Get the primary request.
28 | *
29 | * @return CallRequest The primary request.
30 | */
31 | public function primaryRequest(): CallRequest
32 | {
33 | return $this->primaryRequest;
34 | }
35 |
36 | /**
37 | * Get the secondary requests.
38 | *
39 | * @return array The secondary requests.
40 | */
41 | public function secondaryRequests(): array
42 | {
43 | return $this->secondaryRequests;
44 | }
45 |
46 | /**
47 | * @var CallRequest
48 | */
49 | private $primaryRequest;
50 |
51 | /**
52 | * @var array
53 | */
54 | private $secondaryRequests;
55 | }
56 |
--------------------------------------------------------------------------------
/src/Stub/Answer/Builder/GeneratorAnswerBuilderFactory.php:
--------------------------------------------------------------------------------
1 | invocableInspector = $invocableInspector;
27 | $this->invoker = $invoker;
28 | }
29 |
30 | /**
31 | * Create a generator answer builder for the supplied stub.
32 | *
33 | * @param Stub $stub The stub.
34 | *
35 | * @return GeneratorAnswerBuilder The newly created builder.
36 | */
37 | public function create(Stub $stub): GeneratorAnswerBuilder
38 | {
39 | return new GeneratorAnswerBuilder(
40 | $stub,
41 | $this->invocableInspector,
42 | $this->invoker
43 | );
44 | }
45 |
46 | /**
47 | * @var InvocableInspector
48 | */
49 | private $invocableInspector;
50 |
51 | /**
52 | * @var Invoker
53 | */
54 | private $invoker;
55 | }
56 |
--------------------------------------------------------------------------------
/src/Stub/Answer/Builder/GeneratorYieldFromIteration.php:
--------------------------------------------------------------------------------
1 | $requests The requests.
18 | * @param iterable $values The set of keys and values to yield.
19 | */
20 | public function __construct(array $requests, $values)
21 | {
22 | $this->requests = $requests;
23 | $this->values = $values;
24 | }
25 |
26 | /**
27 | * @var array
28 | */
29 | public $requests;
30 |
31 | /**
32 | * @var iterable
33 | */
34 | public $values;
35 | }
36 |
--------------------------------------------------------------------------------
/src/Stub/Answer/Builder/GeneratorYieldIteration.php:
--------------------------------------------------------------------------------
1 | $requests The requests.
18 | * @param bool $hasKey True if the key should be yielded.
19 | * @param mixed $key The key.
20 | * @param bool $hasValue True if the value should be yielded.
21 | * @param mixed $value The value.
22 | */
23 | public function __construct(
24 | array $requests,
25 | bool $hasKey,
26 | $key,
27 | bool $hasValue,
28 | $value
29 | ) {
30 | $this->requests = $requests;
31 | $this->hasKey = $hasKey;
32 | $this->key = $key;
33 | $this->hasValue = $hasValue;
34 | $this->value = $value;
35 | }
36 |
37 | /**
38 | * @var array
39 | */
40 | public $requests;
41 |
42 | /**
43 | * @var bool
44 | */
45 | public $hasKey;
46 |
47 | /**
48 | * @var mixed
49 | */
50 | public $key;
51 |
52 | /**
53 | * @var bool
54 | */
55 | public $hasValue;
56 |
57 | /**
58 | * @var mixed
59 | */
60 | public $value;
61 | }
62 |
--------------------------------------------------------------------------------
/src/Stub/Answer/CallRequest.php:
--------------------------------------------------------------------------------
1 | callback = $callback;
32 | $this->arguments = $arguments;
33 | $this->prefixSelf = $prefixSelf;
34 | $this->suffixArgumentsObject = $suffixArgumentsObject;
35 | $this->suffixArguments = $suffixArguments;
36 |
37 | foreach ($this->arguments->all() as $index => $argument) {
38 | if ($argument instanceof InstanceHandle) {
39 | $this->arguments->set($index, $argument->get());
40 | }
41 | }
42 | }
43 |
44 | /**
45 | * Get the callback.
46 | *
47 | * @return callable The callback.
48 | */
49 | public function callback(): callable
50 | {
51 | return $this->callback;
52 | }
53 |
54 | /**
55 | * Get the final arguments.
56 | *
57 | * @param mixed $self The self value.
58 | * @param Arguments $arguments The incoming arguments.
59 | *
60 | * @return Arguments The final arguments.
61 | */
62 | public function finalArguments(
63 | mixed $self,
64 | Arguments $arguments
65 | ): Arguments {
66 | $finalArguments = $this->arguments->all();
67 |
68 | if ($this->prefixSelf) {
69 | array_unshift($finalArguments, $self);
70 | }
71 | if ($this->suffixArgumentsObject) {
72 | $finalArguments[] = $arguments;
73 | }
74 | if ($this->suffixArguments) {
75 | $finalArguments = array_merge($finalArguments, $arguments->all());
76 | }
77 |
78 | return new Arguments($finalArguments);
79 | }
80 |
81 | /**
82 | * Get the hard-coded arguments.
83 | *
84 | * @return Arguments The hard-coded arguments.
85 | */
86 | public function arguments(): Arguments
87 | {
88 | return $this->arguments;
89 | }
90 |
91 | /**
92 | * Returns true if the self value should be prefixed to the final arguments.
93 | *
94 | * @return bool True if the self value should be prefixed.
95 | */
96 | public function prefixSelf(): bool
97 | {
98 | return $this->prefixSelf;
99 | }
100 |
101 | /**
102 | * Returns true if the incoming arguments should be appended to the final
103 | * arguments as an object.
104 | *
105 | * @return bool True if arguments object should be appended.
106 | */
107 | public function suffixArgumentsObject(): bool
108 | {
109 | return $this->suffixArgumentsObject;
110 | }
111 |
112 | /**
113 | * Returns true if the incoming arguments should be appended to the final
114 | * arguments.
115 | *
116 | * @return bool True if arguments should be appended.
117 | */
118 | public function suffixArguments(): bool
119 | {
120 | return $this->suffixArguments;
121 | }
122 |
123 | /**
124 | * @var callable
125 | */
126 | private $callback;
127 |
128 | /**
129 | * @var Arguments
130 | */
131 | private $arguments;
132 |
133 | /**
134 | * @var bool
135 | */
136 | private $prefixSelf;
137 |
138 | /**
139 | * @var bool
140 | */
141 | private $suffixArgumentsObject;
142 |
143 | /**
144 | * @var bool
145 | */
146 | private $suffixArguments;
147 | }
148 |
--------------------------------------------------------------------------------
/src/Stub/Exception/FinalReturnTypeException.php:
--------------------------------------------------------------------------------
1 | $criteria The criteria.
20 | * @param AssertionRenderer $assertionRenderer The assertion renderer to use.
21 | */
22 | public function __construct(array $criteria, AssertionRenderer $assertionRenderer)
23 | {
24 | $this->criteria = $criteria;
25 |
26 | parent::__construct(
27 | sprintf(
28 | 'Stub criteria %s were never used. ' .
29 | 'Check for incomplete stub rules.',
30 | var_export(
31 | $assertionRenderer->renderMatchers($criteria),
32 | true
33 | )
34 | )
35 | );
36 | }
37 |
38 | /**
39 | * Get the criteria.
40 | *
41 | * @return array The criteria.
42 | */
43 | public function criteria(): array
44 | {
45 | return $this->criteria;
46 | }
47 |
48 | /**
49 | * @var array
50 | */
51 | private $criteria;
52 | }
53 |
--------------------------------------------------------------------------------
/src/Stub/StubFactory.php:
--------------------------------------------------------------------------------
1 | labelSequencer = $labelSequencer;
46 | $this->matcherFactory = $matcherFactory;
47 | $this->matcherVerifier = $matcherVerifier;
48 | $this->invoker = $invoker;
49 | $this->invocableInspector = $invocableInspector;
50 | $this->emptyValueFactory = $emptyValueFactory;
51 | $this->generatorAnswerBuilderFactory = $generatorAnswerBuilderFactory;
52 | $this->exporter = $exporter;
53 | $this->assertionRenderer = $assertionRenderer;
54 | }
55 |
56 | /**
57 | * Create a new stub.
58 | *
59 | * @param ?callable $callback The callback, or null to create an anonymous stub.
60 | * @param ?callable $defaultAnswerCallback The callback to use when creating a default answer.
61 | *
62 | * @return Stub The newly created stub.
63 | */
64 | public function create(
65 | ?callable $callback,
66 | ?callable $defaultAnswerCallback
67 | ): Stub {
68 | if (null === $defaultAnswerCallback) {
69 | $defaultAnswerCallback =
70 | [StubData::class, 'returnsEmptyAnswerCallback'];
71 | }
72 |
73 | return new StubData(
74 | $callback,
75 | strval($this->labelSequencer->next()),
76 | $defaultAnswerCallback,
77 | $this->matcherFactory,
78 | $this->matcherVerifier,
79 | $this->invoker,
80 | $this->invocableInspector,
81 | $this->emptyValueFactory,
82 | $this->generatorAnswerBuilderFactory,
83 | $this->exporter,
84 | $this->assertionRenderer
85 | );
86 | }
87 |
88 | /**
89 | * @var Sequencer
90 | */
91 | private $labelSequencer;
92 |
93 | /**
94 | * @var MatcherFactory
95 | */
96 | private $matcherFactory;
97 |
98 | /**
99 | * @var MatcherVerifier
100 | */
101 | private $matcherVerifier;
102 |
103 | /**
104 | * @var Invoker
105 | */
106 | private $invoker;
107 |
108 | /**
109 | * @var InvocableInspector
110 | */
111 | private $invocableInspector;
112 |
113 | /**
114 | * @var EmptyValueFactory
115 | */
116 | private $emptyValueFactory;
117 |
118 | /**
119 | * @var GeneratorAnswerBuilderFactory
120 | */
121 | private $generatorAnswerBuilderFactory;
122 |
123 | /**
124 | * @var Exporter
125 | */
126 | private $exporter;
127 |
128 | /**
129 | * @var AssertionRenderer
130 | */
131 | private $assertionRenderer;
132 | }
133 |
--------------------------------------------------------------------------------
/src/Stub/StubRule.php:
--------------------------------------------------------------------------------
1 | $criteria The criteria.
20 | * @param array $answers The answers.
21 | */
22 | public function __construct(array $criteria, array $answers)
23 | {
24 | $this->criteria = $criteria;
25 | $this->answers = $answers;
26 |
27 | $this->lastIndex = count($answers) - 1;
28 | $this->calledCount = 0;
29 | }
30 |
31 | /**
32 | * Get the criteria.
33 | *
34 | * @return array The criteria.
35 | */
36 | public function criteria(): array
37 | {
38 | return $this->criteria;
39 | }
40 |
41 | /**
42 | * Get the answers.
43 | *
44 | * @return array The answers.
45 | */
46 | public function answers(): array
47 | {
48 | return $this->answers;
49 | }
50 |
51 | /**
52 | * Get the next answer.
53 | *
54 | * @return Answer The answer.
55 | * @throws UndefinedAnswerException If an undefined or incomplete answer is encountered.
56 | */
57 | public function next(): Answer
58 | {
59 | if ($this->calledCount > $this->lastIndex) {
60 | $index = $this->lastIndex;
61 | } else {
62 | $index = $this->calledCount;
63 | }
64 |
65 | ++$this->calledCount;
66 |
67 | if (!isset($this->answers[$index])) {
68 | throw new UndefinedAnswerException();
69 | }
70 |
71 | return $this->answers[$index];
72 | }
73 |
74 | /**
75 | * @var array
76 | */
77 | private $criteria;
78 |
79 | /**
80 | * @var array
81 | */
82 | private $answers;
83 |
84 | /**
85 | * @var int
86 | */
87 | private $lastIndex;
88 |
89 | /**
90 | * @var int
91 | */
92 | private $calledCount;
93 | }
94 |
--------------------------------------------------------------------------------
/src/Verification/Cardinality.php:
--------------------------------------------------------------------------------
1 | = 0 && $minimum > $maximum) {
37 | throw new InvalidCardinalityStateException();
38 | }
39 |
40 | if ($maximum < 0 && !$minimum) {
41 | throw new InvalidCardinalityStateException();
42 | }
43 |
44 | $this->minimum = $minimum;
45 | $this->maximum = $maximum;
46 | $this->setIsAlways($isAlways);
47 | }
48 |
49 | /**
50 | * Get the minimum.
51 | *
52 | * @return int The minimum.
53 | */
54 | public function minimum(): int
55 | {
56 | return $this->minimum;
57 | }
58 |
59 | /**
60 | * Get the maximum.
61 | *
62 | * @return int The maximum.
63 | */
64 | public function maximum(): int
65 | {
66 | return $this->maximum;
67 | }
68 |
69 | /**
70 | * Returns true if this cardinality is 'never'.
71 | *
72 | * @return bool True if this cardinality is 'never'.
73 | */
74 | public function isNever(): bool
75 | {
76 | return 0 === $this->maximum;
77 | }
78 |
79 | /**
80 | * Turn 'always' on or off.
81 | *
82 | * @param bool $isAlways True to enable 'always'.
83 | *
84 | * @throws InvalidCardinalityException If the cardinality is invalid.
85 | */
86 | public function setIsAlways(bool $isAlways): void
87 | {
88 | if ($isAlways && $this->isNever()) {
89 | throw new InvalidCardinalityStateException();
90 | }
91 |
92 | $this->isAlways = $isAlways;
93 | }
94 |
95 | /**
96 | * Returns true if 'always' is enabled.
97 | *
98 | * @return bool True if 'always' is enabled.
99 | */
100 | public function isAlways(): bool
101 | {
102 | return $this->isAlways;
103 | }
104 |
105 | /**
106 | * Returns true if the supplied count matches this cardinality.
107 | *
108 | * @param int|bool $count The count or result to check.
109 | * @param int $maximumCount The maximum possible count.
110 | *
111 | * @return bool True if the supplied count matches this cardinality.
112 | */
113 | public function matches($count, int $maximumCount): bool
114 | {
115 | $count = intval($count);
116 | $result = true;
117 |
118 | if ($count < $this->minimum) {
119 | $result = false;
120 | }
121 |
122 | if ($this->maximum >= 0 && $count > $this->maximum) {
123 | $result = false;
124 | }
125 |
126 | if ($this->isAlways && $count < $maximumCount) {
127 | $result = false;
128 | }
129 |
130 | return $result;
131 | }
132 |
133 | /**
134 | * Asserts that this cardinality is suitable for events that can only happen
135 | * once or not at all.
136 | *
137 | * @return $this This cardinality.
138 | * @throws InvalidCardinalityException If the cardinality is invalid.
139 | */
140 | public function assertSingular(): self
141 | {
142 | if ($this->minimum > 1 || $this->maximum > 1 || $this->isAlways) {
143 | throw new InvalidSingularCardinalityException($this);
144 | }
145 |
146 | return $this;
147 | }
148 |
149 | /**
150 | * @var int
151 | */
152 | private $minimum;
153 |
154 | /**
155 | * @var int
156 | */
157 | private $maximum;
158 |
159 | /**
160 | * @var bool
161 | */
162 | private $isAlways;
163 | }
164 |
--------------------------------------------------------------------------------
/src/Verification/CardinalityVerifier.php:
--------------------------------------------------------------------------------
1 | cardinality = new Cardinality(0, 0);
22 |
23 | return $this;
24 | }
25 |
26 | /**
27 | * Requires that the next verification matches only once.
28 | *
29 | * @return $this This verifier.
30 | */
31 | public function once(): CardinalityVerifier
32 | {
33 | $this->cardinality = new Cardinality(1, 1);
34 |
35 | return $this;
36 | }
37 |
38 | /**
39 | * Requires that the next verification matches exactly two times.
40 | *
41 | * @return $this This verifier.
42 | */
43 | public function twice(): CardinalityVerifier
44 | {
45 | $this->cardinality = new Cardinality(2, 2);
46 |
47 | return $this;
48 | }
49 |
50 | /**
51 | * Requires that the next verification matches exactly three times.
52 | *
53 | * @return $this This verifier.
54 | */
55 | public function thrice(): CardinalityVerifier
56 | {
57 | $this->cardinality = new Cardinality(3, 3);
58 |
59 | return $this;
60 | }
61 |
62 | /**
63 | * Requires that the next verification matches an exact number of times.
64 | *
65 | * @param int $times The match count.
66 | *
67 | * @return $this This verifier.
68 | */
69 | public function times(int $times): CardinalityVerifier
70 | {
71 | $this->cardinality = new Cardinality($times, $times);
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * Requires that the next verification matches a number of times greater
78 | * than or equal to $minimum.
79 | *
80 | * @param int $minimum The minimum match count.
81 | *
82 | * @return $this This verifier.
83 | */
84 | public function atLeast(int $minimum): CardinalityVerifier
85 | {
86 | $this->cardinality = new Cardinality($minimum, -1);
87 |
88 | return $this;
89 | }
90 |
91 | /**
92 | * Requires that the next verification matches a number of times less than
93 | * or equal to $maximum.
94 | *
95 | * @param int $maximum The maximum match count.
96 | *
97 | * @return $this This verifier.
98 | */
99 | public function atMost(int $maximum): CardinalityVerifier
100 | {
101 | $this->cardinality = new Cardinality(0, $maximum);
102 |
103 | return $this;
104 | }
105 |
106 | /**
107 | * Requires that the next verification matches a number of times greater
108 | * than or equal to $minimum, and less than or equal to $maximum.
109 | *
110 | * @param int $minimum The minimum match count.
111 | * @param int $maximum The maximum match count.
112 | *
113 | * @return $this This verifier.
114 | * @throws InvalidCardinalityException If the cardinality is invalid.
115 | */
116 | public function between(int $minimum, int $maximum): CardinalityVerifier
117 | {
118 | $this->cardinality = new Cardinality($minimum, $maximum);
119 |
120 | return $this;
121 | }
122 |
123 | /**
124 | * Requires that the next verification matches for all possible items.
125 | *
126 | * @return $this This verifier.
127 | */
128 | public function always(): CardinalityVerifier
129 | {
130 | $this->cardinality->setIsAlways(true);
131 |
132 | return $this;
133 | }
134 |
135 | /**
136 | * Reset the cardinality to its default value.
137 | *
138 | * @return Cardinality The current cardinality.
139 | */
140 | public function resetCardinality(): Cardinality
141 | {
142 | $cardinality = $this->cardinality;
143 | $this->cardinality = new Cardinality();
144 |
145 | return $cardinality;
146 | }
147 |
148 | /**
149 | * Get the cardinality.
150 | *
151 | * @return Cardinality The cardinality.
152 | */
153 | public function cardinality(): Cardinality
154 | {
155 | return $this->cardinality;
156 | }
157 |
158 | /**
159 | * @var Cardinality
160 | */
161 | protected $cardinality;
162 | }
163 |
--------------------------------------------------------------------------------
/src/Verification/Exception/InvalidCardinalityException.php:
--------------------------------------------------------------------------------
1 | cardinality = $cardinality;
25 |
26 | parent::__construct(
27 | 'The specified cardinality is invalid for events ' .
28 | 'that can only happen once or not at all.'
29 | );
30 | }
31 |
32 | /**
33 | * Get the cardinality.
34 | *
35 | * @return Cardinality The cardinality.
36 | */
37 | public function cardinality(): Cardinality
38 | {
39 | return $this->cardinality;
40 | }
41 |
42 | /**
43 | * @var Cardinality
44 | */
45 | private $cardinality;
46 | }
47 |
--------------------------------------------------------------------------------
/src/Verification/GeneratorVerifierFactory.php:
--------------------------------------------------------------------------------
1 | matcherFactory = $matcherFactory;
32 | $this->assertionRecorder = $assertionRecorder;
33 | $this->assertionRenderer = $assertionRenderer;
34 | }
35 |
36 | /**
37 | * Set the call verifier factory.
38 | *
39 | * @param CallVerifierFactory $callVerifierFactory The call verifier factory to use.
40 | */
41 | public function setCallVerifierFactory(
42 | CallVerifierFactory $callVerifierFactory
43 | ): void {
44 | $this->callVerifierFactory = $callVerifierFactory;
45 | }
46 |
47 | /**
48 | * Create a new generator verifier.
49 | *
50 | * @param Spy|Call $subject The subject.
51 | * @param array $calls The calls.
52 | *
53 | * @return GeneratorVerifier The newly created generator verifier.
54 | */
55 | public function create($subject, array $calls): GeneratorVerifier
56 | {
57 | return new GeneratorVerifier(
58 | $subject,
59 | $calls,
60 | $this->matcherFactory,
61 | $this->callVerifierFactory,
62 | $this->assertionRecorder,
63 | $this->assertionRenderer
64 | );
65 | }
66 |
67 | /**
68 | * @var MatcherFactory
69 | */
70 | private $matcherFactory;
71 |
72 | /**
73 | * @var AssertionRecorder
74 | */
75 | private $assertionRecorder;
76 |
77 | /**
78 | * @var AssertionRenderer
79 | */
80 | private $assertionRenderer;
81 |
82 | /**
83 | * @var CallVerifierFactory
84 | */
85 | private $callVerifierFactory;
86 | }
87 |
--------------------------------------------------------------------------------
/src/Verification/IterableVerifierFactory.php:
--------------------------------------------------------------------------------
1 | matcherFactory = $matcherFactory;
32 | $this->assertionRecorder = $assertionRecorder;
33 | $this->assertionRenderer = $assertionRenderer;
34 | }
35 |
36 | /**
37 | * Set the call verifier factory.
38 | *
39 | * @param CallVerifierFactory $callVerifierFactory The call verifier factory to use.
40 | */
41 | public function setCallVerifierFactory(
42 | CallVerifierFactory $callVerifierFactory
43 | ): void {
44 | $this->callVerifierFactory = $callVerifierFactory;
45 | }
46 |
47 | /**
48 | * Create a new iterable verifier.
49 | *
50 | * @param Spy|Call $subject The subject.
51 | * @param array $calls The calls.
52 | *
53 | * @return IterableVerifier The newly created iterable verifier.
54 | */
55 | public function create($subject, array $calls): IterableVerifier
56 | {
57 | return new IterableVerifier(
58 | $subject,
59 | $calls,
60 | $this->matcherFactory,
61 | $this->callVerifierFactory,
62 | $this->assertionRecorder,
63 | $this->assertionRenderer
64 | );
65 | }
66 |
67 | /**
68 | * @var MatcherFactory
69 | */
70 | private $matcherFactory;
71 |
72 | /**
73 | * @var AssertionRecorder
74 | */
75 | private $assertionRecorder;
76 |
77 | /**
78 | * @var AssertionRenderer
79 | */
80 | private $assertionRenderer;
81 |
82 | /**
83 | * @var CallVerifierFactory
84 | */
85 | private $callVerifierFactory;
86 | }
87 |
--------------------------------------------------------------------------------
/src/initialize.php:
--------------------------------------------------------------------------------
1 |