Skip to content

Commit

Permalink
Merge pull request #7 from xepozz/stubs-generator
Browse files Browse the repository at this point in the history
Add stubs generator
  • Loading branch information
xepozz authored Mar 1, 2024
2 parents e128925 + 61b348d commit ac35931
Show file tree
Hide file tree
Showing 10 changed files with 23,203 additions and 29 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
generate-stubs:
cd generator/stub; make generate
32 changes: 24 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ functions as: `time()`, `str_contains()`, `rand`, etc.
- [Tracking calls](#tracking-calls)
- [Global namespaced functions](#global-namespaced-functions)
- [Internal functions](#internal-functions)
- [Workaround](#workaround)
- [Internal function implementation](#internal-function-implementation)
- [Restrictions](#restrictions)
- [Data Providers](#data-providers)
Expand Down Expand Up @@ -76,6 +75,14 @@ The main idea is pretty simple: register a Listener for PHPUnit and call the Moc

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

By default, all functions will be generated and saved into `/vendor/bin/xepozz/internal-mocker/data/mocks.php` file.

Override the first argument of the `Mocker` constructor to change the path:

```php
$mocker = new Mocker('/path/to/your/mocks.php');
```

### Register mocks

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

## Global namespaced functions
### Function signature stubs

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

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

#### Workaround
If you need to add a new function signature, override the second argument of the `Mocker` constructor:

```php
$mocker = new Mocker(stubPath: '/path/to/your/stubs.php');
```

## Global namespaced functions

### Internal functions

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

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

### Data Providers
Expand Down
1 change: 1 addition & 0 deletions generator/stub/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stubs/
8 changes: 8 additions & 0 deletions generator/stub/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
clone:
git clone https://github.com/JetBrains/phpstorm-stubs stubs
clean:
rm -rf stubs
generator:
php generator.php

generate: clean clone generator
55 changes: 55 additions & 0 deletions generator/stub/generator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

$folder = './stubs';
$destination = dirname(__DIR__, 2) . '/src/stubs.php';

$phpFiles = new RegexIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($folder)
),
'/.*\.php$/',
RegexIterator::GET_MATCH
);

$removeAttributesList = implode('|', [
'LanguageLevelTypeAware',
'LanguageAware',
'ElementAvailable',
'PhpStormStubsElementAvailable',
'PhpVersionAware',
'TypeContract',
'ExpectedValues',
'\\\\SensitiveParameter',
]);

$stubs = [];
foreach ($phpFiles as $file) {
$path = $file[0];
$contents = file_get_contents($path);
if (!preg_match_all('/^function (\w+)\(.*\)/m', $contents, $matches, PREG_SET_ORDER)) {
continue;
}

foreach ($matches as $match) {
$functionLine = preg_replace('/#\[(?:' . $removeAttributesList . ')(?:\(.+\)|)] /', '', $match[0]);
//$functionLine = 'function headers_sent(string &$filename, int &$line): bool';
preg_match_all('/(\$\w+)/', $functionLine, $arguments,);
preg_match('/\((.+)\)/', $functionLine, $signatureArguments);

$stubs[$match[1]] = [
'signatureArguments' => $signatureArguments[1] ?? '',
'arguments' => implode(', ', $arguments[0] ?? []),
];
}
}

$patches = require './patches.php';

$result = array_merge($stubs, $patches);

file_put_contents(
$destination,
'<?php' . PHP_EOL . PHP_EOL . 'return ' . var_export($result, true) . ';'
);
13 changes: 13 additions & 0 deletions generator/stub/patches.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

/**
* Example:
* 'header_remove' => [
* 'signatureArguments' => '?string $name = null',
* 'arguments' => '$name',
* ],
*/
return [
];
14 changes: 9 additions & 5 deletions src/Mocker.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

final class Mocker
{
public function __construct(private string $path = __DIR__ . '/../data/mocks.php')
{
public function __construct(
private string $path = __DIR__ . '/../data/mocks.php',
private string $stubPath = __DIR__ . '/stubs.php',
) {
}

public function load(array $mocks): void
Expand Down Expand Up @@ -52,10 +54,13 @@ public function generate(array $mocks): string
}
}
}

$stubs = require $this->stubPath;

$outputs = [];
$mockerConfigClassName = MockerState::class;
foreach ($mocks as $namespace => $functions) {
$innerOutputsString = $this->generateFunction($functions);
$innerOutputsString = $this->generateFunction($functions, $stubs);

$outputs[] = <<<PHP
namespace {$namespace} {
Expand Down Expand Up @@ -101,9 +106,8 @@ private function normalizeMocks(array $mocks): array
return $result;
}

private function generateFunction(mixed $groupedMocks): string
private function generateFunction(array $groupedMocks, array $stubs): string
{
$stubs = require __DIR__ . '/stubs.php';
$innerOutputs = [];
foreach ($groupedMocks as $functionName => $_) {
$signatureArguments = $stubs[$functionName]['signatureArguments'] ?? '...$arguments';
Expand Down
Loading

0 comments on commit ac35931

Please sign in to comment.