Skip to content

Commit 706ad25

Browse files
authored
Switch to class builder instead of shims (#1)
* Switch to class builder instead of shims * Fix sf6; fix ci * Improve class builder * CI fix for CC * Small improvement * Enable xdebug coverage; small fixes
1 parent 07544ed commit 706ad25

14 files changed

+302
-126
lines changed

.gitattributes

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/.gitattributes export-ignore
22
/.github export-ignore
33
/.gitignore export-ignore
4-
/phpunit.xml.dist export-ignore
4+
/phpunit6.xml.dist export-ignore
5+
/phpunit7.xml.dist export-ignore
6+
/phpunit8.xml.dist export-ignore
7+
/phpunit9.xml.dist export-ignore
58
/tests export-ignore

.github/workflows/ci.yml

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: CI
22

3+
env:
4+
XDEBUG_MODE: coverage
5+
36
on:
47
push:
58
branches:
@@ -13,27 +16,55 @@ jobs:
1316
runs-on: ubuntu-latest
1417
strategy:
1518
matrix:
16-
php: [ '7.0', '7.1', '7.2', '7.3', '7.4', '8.0' ]
19+
include:
20+
- php: '7.0'
21+
phpunit: '6'
22+
symfony: '3'
23+
- php: '7.1'
24+
phpunit: '7'
25+
symfony: '4'
26+
- php: '7.2'
27+
phpunit: '8'
28+
symfony: '5'
29+
- php: '7.3'
30+
phpunit: '9'
31+
symfony: '5'
32+
- php: '7.4'
33+
phpunit: '9'
34+
symfony: '5'
35+
- php: '8.0'
36+
phpunit: '9'
37+
symfony: '6'
1738

1839
steps:
19-
- name: Set up PHP
20-
uses: shivammathur/setup-php@v2
21-
with:
22-
php-version: ${{ matrix.php }}
23-
coverage: xdebug2
24-
2540
- name: Checkout code
26-
uses: actions/checkout@v2
41+
uses: actions/checkout@v3
2742
with:
2843
fetch-depth: 2
2944

30-
- name: Download dependencies
31-
uses: ramsey/composer-install@v1
45+
- name: Cache Composer dependencies
46+
uses: actions/cache@v3
3247
with:
33-
composer-options: --no-interaction --prefer-dist --optimize-autoloader
48+
path: /tmp/composer-cache
49+
key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
50+
51+
- name: Set up PHP
52+
uses: php-actions/composer@v6
53+
with:
54+
php_version: ${{ matrix.php }}
55+
version: 2.2
56+
php_extensions: xdebug
57+
command: require
58+
only_args: symfony/expression-language:~${{ matrix.symfony }}
3459

3560
- name: Run tests
36-
run: ./vendor/bin/phpunit --coverage-clover coverage.xml
61+
uses: php-actions/composer@v6
62+
with:
63+
php_version: ${{ matrix.php }}
64+
version: 2.2
65+
php_extensions: xdebug
66+
command: run-tests
67+
only_args: -- --configuration phpunit${{ matrix.phpunit }}.xml.dist
3768

3869
- name: Upload to Codecov
3970
env:

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ vendor/
33
*.cache
44
*.iml
55
composer.lock
6-
phpunit.xml
6+
phpunit*.xml

composer.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@
55
"readme": "README.md",
66
"license": "MIT",
77
"description": "Template String support for Symfony Expression Language",
8-
"keywords": ["expression", "language", "dsl", "symfony", "template", "string", "uuf6429"],
8+
"keywords": [
9+
"expression",
10+
"language",
11+
"dsl",
12+
"symfony",
13+
"template",
14+
"string",
15+
"uuf6429"
16+
],
917
"authors": [
1018
{
1119
"name": "Christian Sciberras",
@@ -28,5 +36,8 @@
2836
"psr-4": {
2937
"uuf6429\\ExpressionLanguage\\": "tests"
3038
}
39+
},
40+
"scripts": {
41+
"run-tests": "./vendor/bin/phpunit --coverage-clover coverage.xml"
3142
}
3243
}

phpunit6.xml.dist

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.0/phpunit.xsd">
4+
<testsuites>
5+
<testsuite name="All Tests">
6+
<directory>./tests</directory>
7+
</testsuite>
8+
</testsuites>
9+
<filter>
10+
<whitelist processUncoveredFilesFromWhitelist="true">
11+
<directory suffix=".php">./src</directory>
12+
<exclude>
13+
<directory suffix=".php">./vendor</directory>
14+
</exclude>
15+
</whitelist>
16+
</filter>
17+
</phpunit>

phpunit7.xml.dist

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.0/phpunit.xsd">
4+
<testsuites>
5+
<testsuite name="All Tests">
6+
<directory>./tests</directory>
7+
</testsuite>
8+
</testsuites>
9+
<filter>
10+
<whitelist processUncoveredFilesFromWhitelist="true">
11+
<directory suffix=".php">./src</directory>
12+
<exclude>
13+
<directory suffix=".php">./vendor</directory>
14+
</exclude>
15+
</whitelist>
16+
</filter>
17+
</phpunit>

phpunit8.xml.dist

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd">
4+
<testsuites>
5+
<testsuite name="All Tests">
6+
<directory>./tests</directory>
7+
</testsuite>
8+
</testsuites>
9+
<filter>
10+
<whitelist processUncoveredFilesFromWhitelist="true">
11+
<directory suffix=".php">./src</directory>
12+
<exclude>
13+
<directory suffix=".php">./vendor</directory>
14+
</exclude>
15+
</whitelist>
16+
</filter>
17+
</phpunit>
File renamed without changes.

src/ClassBuilder.php

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
namespace uuf6429\ExpressionLanguage;
4+
5+
use JetBrains\PhpStorm\Language;
6+
use ReflectionException;
7+
use ReflectionMethod;
8+
9+
/**
10+
* @codeCoverageIgnore
11+
*/
12+
class ClassBuilder
13+
{
14+
/**
15+
* @var string
16+
*/
17+
private $parent;
18+
19+
/**
20+
* @var string
21+
*/
22+
private $child;
23+
24+
/**
25+
* @var string
26+
*/
27+
private $usedTraits = [];
28+
29+
/**
30+
* @var string
31+
*/
32+
private $usedImports = [];
33+
34+
/**
35+
* @var array<string, string>
36+
*/
37+
private $overriddenMethods = [];
38+
39+
public static function create(): ClassBuilder
40+
{
41+
return new self();
42+
}
43+
44+
public function class(string $fqn): ClassBuilder
45+
{
46+
$this->child = '\\' . ltrim($fqn, '\\');
47+
return $this;
48+
}
49+
50+
public function extend(string $fqn): ClassBuilder
51+
{
52+
$this->parent = '\\' . ltrim($fqn, '\\');
53+
return $this;
54+
}
55+
56+
public function use(string $fqn): ClassBuilder
57+
{
58+
$this->usedTraits[] = '\\' . ltrim($fqn, '\\');
59+
return $this;
60+
}
61+
62+
public function import(string $fqn): ClassBuilder
63+
{
64+
$this->usedImports[] = '\\' . ltrim($fqn, '\\');
65+
return $this;
66+
}
67+
68+
public function override(
69+
string $method,
70+
#[Language('InjectablePHP')]
71+
string $body
72+
): ClassBuilder {
73+
$this->overriddenMethods[$method] = $body;
74+
return $this;
75+
}
76+
77+
public function buildClass(): string
78+
{
79+
80+
list($class, $namespace) = array_map('strrev', explode('\\', strrev($this->child), 2)) + [''];
81+
82+
$codeLines = [
83+
'',
84+
sprintf('namespace %s;', ltrim($namespace, '\\')),
85+
'',
86+
];
87+
88+
foreach ($this->usedImports as $import) {
89+
$codeLines[] = "use $import;";
90+
}
91+
92+
$codeLines[] = '';
93+
94+
$codeLines[] = "class $class extends $this->parent";
95+
$codeLines[] = '{';
96+
97+
foreach ($this->usedTraits as $trait) {
98+
$codeLines[] = " use $trait;";
99+
}
100+
101+
foreach ($this->overriddenMethods as $method => $body) {
102+
if (($sig = $this->extractSignature($this->parent, $method)) !== null) {
103+
$codeLines[] = '';
104+
array_push($codeLines, ...$sig);
105+
$codeLines[] = ' {';
106+
$codeLines[] = " $body";
107+
$codeLines[] = ' }';
108+
}
109+
}
110+
111+
$codeLines[] = '}';
112+
113+
return implode("\n", $codeLines);
114+
}
115+
116+
public function createClass()
117+
{
118+
eval($this->buildClass());
119+
}
120+
121+
/**
122+
* @param string $class
123+
* @param string $method
124+
* @return string[]|null
125+
*/
126+
private function extractSignature(string $class, string $method)
127+
{
128+
try {
129+
$code = file_get_contents(
130+
(new ReflectionMethod($class, $method))->getFileName()
131+
);
132+
133+
return preg_match("/([^}]+function {$method}[^{]+?)\\n\\s+?{/", $code, $matches)
134+
? array_filter(explode("\n", $matches[1]))
135+
: null;
136+
} catch (ReflectionException $ex) {
137+
return null;
138+
}
139+
}
140+
}

src/ExpressionLanguageWithTplStr.php

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,42 @@
22

33
namespace uuf6429\ExpressionLanguage;
44

5-
use Throwable;
5+
use Symfony\Component\ExpressionLanguage\Expression as SymfonyExpression;
6+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage as SymfonyExpressionLanguage;
7+
use Symfony\Component\ExpressionLanguage\ParsedExpression as SymfonyParsedExpression;
68

7-
$instantiable = static function ($class) {
8-
try {
9-
new $class();
10-
return true;
11-
} catch (Throwable $ex) {
12-
return false;
13-
}
14-
};
9+
ClassBuilder::create()
10+
->import(SymfonyExpression::class)
11+
->import(SymfonyParsedExpression::class)
12+
->class(ExpressionLanguageWithTplStr::class)
13+
->extend(SymfonyExpressionLanguage::class)
14+
->use(TemplateStringTranslatorTrait::class)
15+
->override(
16+
'compile',
17+
'return parent::compile($this->convertExpression($expression), $names);'
18+
)
19+
->override(
20+
'evaluate',
21+
'return parent::evaluate($this->convertExpression($expression), $values);'
22+
)
23+
->override(
24+
'parse',
25+
'return parent::parse($this->convertExpression($expression), $names);'
26+
)
27+
->override(
28+
'lint',
29+
'parent::lint($this->convertExpression($expression), $names);'
30+
)
31+
->createClass();
1532

16-
if ($instantiable(Shims\ExpressionLanguageWithTplStrSF6::class)) {
17-
class ExpressionLanguageWithTplStr extends Shims\ExpressionLanguageWithTplStrSF6
18-
{
19-
}
20-
} elseif ($instantiable(Shims\ExpressionLanguageWithTplStrSF5::class)) {
21-
class ExpressionLanguageWithTplStr extends Shims\ExpressionLanguageWithTplStrSF5
22-
{
23-
}
24-
} elseif ($instantiable(Shims\ExpressionLanguageWithTplStrSF4::class)) {
25-
class ExpressionLanguageWithTplStr extends Shims\ExpressionLanguageWithTplStrSF4
33+
/**
34+
* The class definition below will probably never be executed since the real one is generated by
35+
* the ClassBuilder above. However, the code below is still useful to enable IDE autocompletion.
36+
* @codeCoverageIgnore
37+
*/
38+
if (!class_exists(ExpressionLanguageWithTplStr::class)) {
39+
class ExpressionLanguageWithTplStr extends SymfonyExpressionLanguage
2640
{
41+
use TemplateStringTranslatorTrait;
2742
}
2843
}

0 commit comments

Comments
 (0)