Skip to content

Commit ac35931

Browse files
authored
Merge pull request #7 from xepozz/stubs-generator
Add stubs generator
2 parents e128925 + 61b348d commit ac35931

File tree

10 files changed

+23203
-29
lines changed

10 files changed

+23203
-29
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
generate-stubs:
2+
cd generator/stub; make generate

README.md

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ functions as: `time()`, `str_contains()`, `rand`, etc.
2020
- [Tracking calls](#tracking-calls)
2121
- [Global namespaced functions](#global-namespaced-functions)
2222
- [Internal functions](#internal-functions)
23-
- [Workaround](#workaround)
2423
- [Internal function implementation](#internal-function-implementation)
2524
- [Restrictions](#restrictions)
2625
- [Data Providers](#data-providers)
@@ -76,6 +75,14 @@ The main idea is pretty simple: register a Listener for PHPUnit and call the Moc
7675

7776
Here you have registered extension that will be called every time when you run `./vendor/bin/phpunit`.
7877

78+
By default, all functions will be generated and saved into `/vendor/bin/xepozz/internal-mocker/data/mocks.php` file.
79+
80+
Override the first argument of the `Mocker` constructor to change the path:
81+
82+
```php
83+
$mocker = new Mocker('/path/to/your/mocks.php');
84+
```
85+
7986
### Register mocks
8087

8188
The package supports a few ways to mock functions:
@@ -216,16 +223,22 @@ $traces = MockerState::getTraces('App\Service', 'time');
216223
]
217224
```
218225

219-
## Global namespaced functions
226+
### Function signature stubs
220227

221-
### Internal functions
228+
All internal functions are stubbed to be compatible with the original ones.
229+
It makes the functions use referenced arguments (`&$file`) as the originals do.
222230

223-
Without any additional configuration you can mock only functions that are defined under any not global
224-
namespaces: `App\`, `App\Service\`, etc.
225-
But you cannot mock functions that are defined under global namespace or defined in a `use` statement, e.g. `use time;`
226-
or `\time();`.
231+
They are located in the [`src/stubs.php`](src/stubs.php) file.
227232

228-
#### Workaround
233+
If you need to add a new function signature, override the second argument of the `Mocker` constructor:
234+
235+
```php
236+
$mocker = new Mocker(stubPath: '/path/to/your/stubs.php');
237+
```
238+
239+
## Global namespaced functions
240+
241+
### Internal functions
229242

230243
The way you can mock global functions is to disable them
231244
in `php.ini`: https://www.php.net/manual/en/ini.core.php#ini.disable-functions
@@ -269,6 +282,9 @@ $mocks[] = [
269282
];
270283
```
271284

285+
> Keep in mind that leaving a global function without implementation will cause a recourse call of the function,
286+
> that will lead to a fatal error.
287+
272288
## Restrictions
273289

274290
### Data Providers

generator/stub/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
stubs/

generator/stub/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
clone:
2+
git clone https://github.com/JetBrains/phpstorm-stubs stubs
3+
clean:
4+
rm -rf stubs
5+
generator:
6+
php generator.php
7+
8+
generate: clean clone generator

generator/stub/generator.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
$folder = './stubs';
6+
$destination = dirname(__DIR__, 2) . '/src/stubs.php';
7+
8+
$phpFiles = new RegexIterator(
9+
new RecursiveIteratorIterator(
10+
new RecursiveDirectoryIterator($folder)
11+
),
12+
'/.*\.php$/',
13+
RegexIterator::GET_MATCH
14+
);
15+
16+
$removeAttributesList = implode('|', [
17+
'LanguageLevelTypeAware',
18+
'LanguageAware',
19+
'ElementAvailable',
20+
'PhpStormStubsElementAvailable',
21+
'PhpVersionAware',
22+
'TypeContract',
23+
'ExpectedValues',
24+
'\\\\SensitiveParameter',
25+
]);
26+
27+
$stubs = [];
28+
foreach ($phpFiles as $file) {
29+
$path = $file[0];
30+
$contents = file_get_contents($path);
31+
if (!preg_match_all('/^function (\w+)\(.*\)/m', $contents, $matches, PREG_SET_ORDER)) {
32+
continue;
33+
}
34+
35+
foreach ($matches as $match) {
36+
$functionLine = preg_replace('/#\[(?:' . $removeAttributesList . ')(?:\(.+\)|)] /', '', $match[0]);
37+
//$functionLine = 'function headers_sent(string &$filename, int &$line): bool';
38+
preg_match_all('/(\$\w+)/', $functionLine, $arguments,);
39+
preg_match('/\((.+)\)/', $functionLine, $signatureArguments);
40+
41+
$stubs[$match[1]] = [
42+
'signatureArguments' => $signatureArguments[1] ?? '',
43+
'arguments' => implode(', ', $arguments[0] ?? []),
44+
];
45+
}
46+
}
47+
48+
$patches = require './patches.php';
49+
50+
$result = array_merge($stubs, $patches);
51+
52+
file_put_contents(
53+
$destination,
54+
'<?php' . PHP_EOL . PHP_EOL . 'return ' . var_export($result, true) . ';'
55+
);

generator/stub/patches.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Example:
7+
* 'header_remove' => [
8+
* 'signatureArguments' => '?string $name = null',
9+
* 'arguments' => '$name',
10+
* ],
11+
*/
12+
return [
13+
];

src/Mocker.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
final class Mocker
1010
{
11-
public function __construct(private string $path = __DIR__ . '/../data/mocks.php')
12-
{
11+
public function __construct(
12+
private string $path = __DIR__ . '/../data/mocks.php',
13+
private string $stubPath = __DIR__ . '/stubs.php',
14+
) {
1315
}
1416

1517
public function load(array $mocks): void
@@ -52,10 +54,13 @@ public function generate(array $mocks): string
5254
}
5355
}
5456
}
57+
58+
$stubs = require $this->stubPath;
59+
5560
$outputs = [];
5661
$mockerConfigClassName = MockerState::class;
5762
foreach ($mocks as $namespace => $functions) {
58-
$innerOutputsString = $this->generateFunction($functions);
63+
$innerOutputsString = $this->generateFunction($functions, $stubs);
5964

6065
$outputs[] = <<<PHP
6166
namespace {$namespace} {
@@ -101,9 +106,8 @@ private function normalizeMocks(array $mocks): array
101106
return $result;
102107
}
103108

104-
private function generateFunction(mixed $groupedMocks): string
109+
private function generateFunction(array $groupedMocks, array $stubs): string
105110
{
106-
$stubs = require __DIR__ . '/stubs.php';
107111
$innerOutputs = [];
108112
foreach ($groupedMocks as $functionName => $_) {
109113
$signatureArguments = $stubs[$functionName]['signatureArguments'] ?? '...$arguments';

0 commit comments

Comments
 (0)