1010namespace PHPUnit \Framework \MockObject ;
1111
1212use function array_any ;
13+ use function array_unique ;
14+ use function array_values ;
15+ use function in_array ;
1316use function strtolower ;
1417use Exception ;
15- use PHPUnit \Framework \MockObject \Rule \AnyInvokedCount ;
18+ use PHPUnit \Framework \MockObject \Rule \InvocationOrder ;
19+ use PHPUnit \Framework \MockObject \Rule \InvokedCount ;
20+ use PHPUnit \Framework \MockObject \Rule \MethodName ;
1621use Throwable ;
1722
1823/**
1924 * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
2025 *
2126 * @internal This class is not covered by the backward compatibility promise for PHPUnit
2227 */
23- abstract class InvocationHandler
28+ final class InvocationHandler
2429{
25- /**
26- * @var list<ConfigurableMethod>
27- */
28- protected readonly array $ configurableMethods ;
29-
3030 /**
3131 * @var list<Matcher>
3232 */
@@ -36,16 +36,28 @@ abstract class InvocationHandler
3636 * @var array<non-empty-string, Matcher>
3737 */
3838 private array $ matcherMap = [];
39+
40+ /**
41+ * @var list<ConfigurableMethod>
42+ */
43+ private readonly array $ configurableMethods ;
3944 private readonly bool $ returnValueGeneration ;
45+ private readonly bool $ isMockObject ;
4046 private bool $ sealed = false ;
4147
4248 /**
4349 * @param list<ConfigurableMethod> $configurableMethods
4450 */
45- public function __construct (array $ configurableMethods , bool $ returnValueGeneration )
51+ public function __construct (array $ configurableMethods , bool $ returnValueGeneration, bool $ isMockObject = false )
4652 {
4753 $ this ->configurableMethods = $ configurableMethods ;
4854 $ this ->returnValueGeneration = $ returnValueGeneration ;
55+ $ this ->isMockObject = $ isMockObject ;
56+ }
57+
58+ public function isMockObject (): bool
59+ {
60+ return $ this ->isMockObject ;
4961 }
5062
5163 public function hasMatchers (): bool
@@ -86,16 +98,23 @@ public function registerMatcher(string $id, Matcher $matcher): void
8698 /**
8799 * @throws TestDoubleSealedException
88100 */
89- public function configureStub ( ): InvocationStubber
101+ public function expects ( InvocationOrder $ rule ): InvocationMocker | InvocationStubber
90102 {
91- if ($ this ->isSealed () ) {
103+ if ($ this ->sealed ) {
92104 throw new TestDoubleSealedException ;
93105 }
94106
95- $ matcher = new Matcher (new AnyInvokedCount );
96-
107+ $ matcher = new Matcher ($ rule );
97108 $ this ->addMatcher ($ matcher );
98109
110+ if ($ this ->isMockObject ) {
111+ return new InvocationMockerImplementation (
112+ $ this ,
113+ $ matcher ,
114+ ...$ this ->configurableMethods ,
115+ );
116+ }
117+
99118 return new InvocationStubberImplementation (
100119 $ this ,
101120 $ matcher ,
@@ -157,28 +176,64 @@ public function verify(): void
157176 }
158177 }
159178
160- public function isSealed ( ): bool
179+ public function seal ( bool $ isMockObject ): void
161180 {
162- return $ this ->sealed ;
163- }
181+ if ($ this ->sealed ) {
182+ return ;
183+ }
184+
185+ $ this ->sealed = true ;
186+
187+ if (!$ isMockObject ) {
188+ return ;
189+ }
190+
191+ $ configuredMethods = $ this ->configuredMethodNames ();
192+
193+ foreach ($ this ->configurableMethods as $ method ) {
194+ if (!in_array ($ method ->name (), $ configuredMethods , true )) {
195+ $ matcher = new Matcher (new InvokedCount (0 ));
164196
165- abstract public function seal (): void ;
197+ $ matcher -> setMethodNameRule ( new MethodName ( $ method -> name ())) ;
166198
167- protected function addMatcher (Matcher $ matcher ): void
199+ $ this ->addMatcher ($ matcher );
200+ }
201+ }
202+ }
203+
204+ public function isSealed (): bool
168205 {
169- $ this ->matchers [] = $ matcher ;
206+ return $ this ->sealed ;
170207 }
171208
172- protected function markSealed ( ): void
209+ private function addMatcher ( Matcher $ matcher ): void
173210 {
174- $ this ->sealed = true ;
211+ $ this ->matchers [] = $ matcher ;
175212 }
176213
177214 /**
178- * @return list<Matcher>
215+ * Returns the list of method names that have been configured with expectations.
216+ * Only considers exact string matches for method names.
217+ * Methods with any() expectation are considered configured.
218+ *
219+ * @return list<non-empty-string>
179220 */
180- protected function matchers (): array
221+ private function configuredMethodNames (): array
181222 {
182- return $ this ->matchers ;
223+ $ names = [];
224+
225+ foreach ($ this ->matchers as $ matcher ) {
226+ if (!$ matcher ->hasMethodNameRule ()) {
227+ continue ;
228+ }
229+
230+ foreach ($ this ->configurableMethods as $ method ) {
231+ if ($ matcher ->methodNameRule ()->matchesName ($ method ->name ())) {
232+ $ names [] = $ method ->name ();
233+ }
234+ }
235+ }
236+
237+ return array_values (array_unique ($ names ));
183238 }
184239}
0 commit comments