22
33namespace phpmock \phpunit ;
44
5+ use DirectoryIterator ;
56use phpmock \integration \MockDelegateFunctionBuilder ;
67use phpmock \MockBuilder ;
78use phpmock \Deactivatable ;
89use PHPUnit \Event \Facade ;
910use PHPUnit \Framework \MockObject \MockObject ;
11+ use ReflectionClass ;
1012use ReflectionProperty ;
13+ use SebastianBergmann \Template \Template ;
1114
1215/**
1316 * Adds building a function mock functionality into \PHPUnit\Framework\TestCase.
3841 */
3942trait PHPMock
4043{
44+ public static $ templatesPath = '/tmp ' ;
45+
46+ private $ phpunitVersionClass = '\\PHPUnit \\Runner \\Version ' ;
47+ private $ openInvocation = 'new \\PHPUnit \\Framework \\MockObject \\Invocation( ' ;
48+ private $ openWrapper = '\\phpmock \\phpunit \\DefaultArgumentRemover::removeDefaultArgumentsWithReflection( ' ;
49+ private $ closeFunc = ') ' ;
50+
4151 /**
4252 * Returns the enabled function mock.
4353 *
@@ -50,6 +60,8 @@ trait PHPMock
5060 */
5161 public function getFunctionMock ($ namespace , $ name )
5262 {
63+ $ this ->prepareCustomTemplates ();
64+
5365 $ delegateBuilder = new MockDelegateFunctionBuilder ();
5466 $ delegateBuilder ->build ($ name );
5567
@@ -70,8 +82,7 @@ public function getFunctionMock($namespace, $name)
7082
7183 $ this ->registerForTearDown ($ functionMock );
7284
73- $ proxy = new MockObjectProxy ($ mock );
74- return $ proxy ;
85+ return new MockObjectProxy ($ mock );
7586 }
7687
7788 private function addMatcher ($ mock , $ name )
@@ -145,4 +156,130 @@ public static function defineFunctionMock($namespace, $name)
145156 ->build ()
146157 ->define ();
147158 }
159+
160+ /**
161+ * Adds a wrapper method to the Invocable object instance that makes it
162+ * possible to remove optional parameters when it is declared read-only.
163+ *
164+ * @return void
165+ *
166+ * @SuppressWarnings(PHPMD.StaticAccess)
167+ * @SuppressWarnings(PHPMD.IfStatementAssignment)
168+ */
169+ private function prepareCustomTemplates ()
170+ {
171+ if (!($ this ->shouldPrepareCustomTemplates () &&
172+ is_dir (static ::$ templatesPath ) &&
173+ ($ phpunitTemplatesDir = $ this ->getPhpunitTemplatesDir ())
174+ )) {
175+ return ;
176+ }
177+
178+ $ templatesDir = realpath (static ::$ templatesPath );
179+ $ directoryIterator = new DirectoryIterator ($ phpunitTemplatesDir );
180+
181+ $ templates = [];
182+
183+ $ prefix = 'phpmock-phpunit- ' . $ this ->getPhpUnitVersion () . '- ' ;
184+
185+ foreach ($ directoryIterator as $ fileinfo ) {
186+ if ($ fileinfo ->getExtension () !== 'tpl ' ) {
187+ continue ;
188+ }
189+
190+ $ filename = $ fileinfo ->getFilename ();
191+ $ customTemplateFile = $ templatesDir . DIRECTORY_SEPARATOR . $ prefix . $ filename ;
192+ $ templateFile = $ phpunitTemplatesDir . DIRECTORY_SEPARATOR . $ filename ;
193+
194+ $ this ->createCustomTemplateFile ($ templateFile , $ customTemplateFile );
195+
196+ if (file_exists ($ customTemplateFile )) {
197+ $ templates [$ templateFile ] = new Template ($ customTemplateFile );
198+ }
199+ }
200+
201+ $ mockMethodClasses = [
202+ 'PHPUnit \\Framework \\MockObject \\Generator \\MockMethod ' ,
203+ 'PHPUnit \\Framework \\MockObject \\MockMethod ' ,
204+ ];
205+
206+ foreach ($ mockMethodClasses as $ mockMethodClass ) {
207+ if (class_exists ($ mockMethodClass )) {
208+ $ reflection = new ReflectionClass ($ mockMethodClass );
209+
210+ $ reflectionTemplates = $ reflection ->getProperty ('templates ' );
211+ $ reflectionTemplates ->setAccessible (true );
212+
213+ $ reflectionTemplates ->setValue ($ templates );
214+
215+ break ;
216+ }
217+ }
218+ }
219+
220+ private function shouldPrepareCustomTemplates ()
221+ {
222+ return class_exists ($ this ->phpunitVersionClass )
223+ && version_compare ($ this ->getPhpUnitVersion (), '10.0.0 ' ) >= 0 ;
224+ }
225+
226+ private function getPhpUnitVersion ()
227+ {
228+ return call_user_func ([$ this ->phpunitVersionClass , 'id ' ]);
229+ }
230+
231+ /**
232+ * Detects the PHPUnit templates dir
233+ *
234+ * @return string|null
235+ */
236+ private function getPhpunitTemplatesDir ()
237+ {
238+ $ phpunitLocations = [
239+ __DIR__ . '/../../ ' ,
240+ __DIR__ . '/../vendor/ ' ,
241+ ];
242+
243+ $ phpunitRelativePath = '/phpunit/phpunit/src/Framework/MockObject/Generator ' ;
244+
245+ foreach ($ phpunitLocations as $ prefix ) {
246+ $ possibleDirs = [
247+ $ prefix . $ phpunitRelativePath . '/templates ' ,
248+ $ prefix . $ phpunitRelativePath ,
249+ ];
250+
251+ foreach ($ possibleDirs as $ dir ) {
252+ if (is_dir ($ dir )) {
253+ return realpath ($ dir );
254+ }
255+ }
256+ }
257+ }
258+
259+ /**
260+ * Clones original template with the wrapper
261+ *
262+ * @param string $templateFile Template filename
263+ * @param string $customTemplateFile Custom template filename
264+ *
265+ * @return void
266+ *
267+ * @SuppressWarnings(PHPMD.IfStatementAssignment)
268+ */
269+ private function createCustomTemplateFile (string $ templateFile , string $ customTemplateFile )
270+ {
271+ $ template = file_get_contents ($ templateFile );
272+
273+ if (($ start = strpos ($ template , $ this ->openInvocation )) !== false &&
274+ ($ end = strpos ($ template , $ this ->closeFunc , $ start )) !== false
275+ ) {
276+ $ template = substr_replace ($ template , $ this ->closeFunc , $ end , 0 );
277+ $ template = substr_replace ($ template , $ this ->openWrapper , $ start , 0 );
278+
279+ if ($ file = fopen ($ customTemplateFile , 'w+ ' )) {
280+ fputs ($ file , $ template );
281+ fclose ($ file );
282+ }
283+ }
284+ }
148285}
0 commit comments