Skip to content

Commit

Permalink
Switch to class builder instead of shims (#1)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
uuf6429 authored Dec 24, 2022
1 parent 07544ed commit 706ad25
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 126 deletions.
5 changes: 4 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/phpunit.xml.dist export-ignore
/phpunit6.xml.dist export-ignore
/phpunit7.xml.dist export-ignore
/phpunit8.xml.dist export-ignore
/phpunit9.xml.dist export-ignore
/tests export-ignore
55 changes: 43 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: CI

env:
XDEBUG_MODE: coverage

on:
push:
branches:
Expand All @@ -13,27 +16,55 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: [ '7.0', '7.1', '7.2', '7.3', '7.4', '8.0' ]
include:
- php: '7.0'
phpunit: '6'
symfony: '3'
- php: '7.1'
phpunit: '7'
symfony: '4'
- php: '7.2'
phpunit: '8'
symfony: '5'
- php: '7.3'
phpunit: '9'
symfony: '5'
- php: '7.4'
phpunit: '9'
symfony: '5'
- php: '8.0'
phpunit: '9'
symfony: '6'

steps:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug2

- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 2

- name: Download dependencies
uses: ramsey/composer-install@v1
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
composer-options: --no-interaction --prefer-dist --optimize-autoloader
path: /tmp/composer-cache
key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}

- name: Set up PHP
uses: php-actions/composer@v6
with:
php_version: ${{ matrix.php }}
version: 2.2
php_extensions: xdebug
command: require
only_args: symfony/expression-language:~${{ matrix.symfony }}

- name: Run tests
run: ./vendor/bin/phpunit --coverage-clover coverage.xml
uses: php-actions/composer@v6
with:
php_version: ${{ matrix.php }}
version: 2.2
php_extensions: xdebug
command: run-tests
only_args: -- --configuration phpunit${{ matrix.phpunit }}.xml.dist

- name: Upload to Codecov
env:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ vendor/
*.cache
*.iml
composer.lock
phpunit.xml
phpunit*.xml
13 changes: 12 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
"readme": "README.md",
"license": "MIT",
"description": "Template String support for Symfony Expression Language",
"keywords": ["expression", "language", "dsl", "symfony", "template", "string", "uuf6429"],
"keywords": [
"expression",
"language",
"dsl",
"symfony",
"template",
"string",
"uuf6429"
],
"authors": [
{
"name": "Christian Sciberras",
Expand All @@ -28,5 +36,8 @@
"psr-4": {
"uuf6429\\ExpressionLanguage\\": "tests"
}
},
"scripts": {
"run-tests": "./vendor/bin/phpunit --coverage-clover coverage.xml"
}
}
17 changes: 17 additions & 0 deletions phpunit6.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.0/phpunit.xsd">
<testsuites>
<testsuite name="All Tests">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
<exclude>
<directory suffix=".php">./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
17 changes: 17 additions & 0 deletions phpunit7.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.0/phpunit.xsd">
<testsuites>
<testsuite name="All Tests">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
<exclude>
<directory suffix=".php">./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
17 changes: 17 additions & 0 deletions phpunit8.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd">
<testsuites>
<testsuite name="All Tests">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
<exclude>
<directory suffix=".php">./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
File renamed without changes.
140 changes: 140 additions & 0 deletions src/ClassBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

namespace uuf6429\ExpressionLanguage;

use JetBrains\PhpStorm\Language;
use ReflectionException;
use ReflectionMethod;

/**
* @codeCoverageIgnore
*/
class ClassBuilder
{
/**
* @var string
*/
private $parent;

/**
* @var string
*/
private $child;

/**
* @var string
*/
private $usedTraits = [];

/**
* @var string
*/
private $usedImports = [];

/**
* @var array<string, string>
*/
private $overriddenMethods = [];

public static function create(): ClassBuilder
{
return new self();
}

public function class(string $fqn): ClassBuilder
{
$this->child = '\\' . ltrim($fqn, '\\');
return $this;
}

public function extend(string $fqn): ClassBuilder
{
$this->parent = '\\' . ltrim($fqn, '\\');
return $this;
}

public function use(string $fqn): ClassBuilder
{
$this->usedTraits[] = '\\' . ltrim($fqn, '\\');
return $this;
}

public function import(string $fqn): ClassBuilder
{
$this->usedImports[] = '\\' . ltrim($fqn, '\\');
return $this;
}

public function override(
string $method,
#[Language('InjectablePHP')]
string $body
): ClassBuilder {
$this->overriddenMethods[$method] = $body;
return $this;
}

public function buildClass(): string
{

list($class, $namespace) = array_map('strrev', explode('\\', strrev($this->child), 2)) + [''];

$codeLines = [
'',
sprintf('namespace %s;', ltrim($namespace, '\\')),
'',
];

foreach ($this->usedImports as $import) {
$codeLines[] = "use $import;";
}

$codeLines[] = '';

$codeLines[] = "class $class extends $this->parent";
$codeLines[] = '{';

foreach ($this->usedTraits as $trait) {
$codeLines[] = " use $trait;";
}

foreach ($this->overriddenMethods as $method => $body) {
if (($sig = $this->extractSignature($this->parent, $method)) !== null) {
$codeLines[] = '';
array_push($codeLines, ...$sig);
$codeLines[] = ' {';
$codeLines[] = " $body";
$codeLines[] = ' }';
}
}

$codeLines[] = '}';

return implode("\n", $codeLines);
}

public function createClass()
{
eval($this->buildClass());
}

/**
* @param string $class
* @param string $method
* @return string[]|null
*/
private function extractSignature(string $class, string $method)
{
try {
$code = file_get_contents(
(new ReflectionMethod($class, $method))->getFileName()
);

return preg_match("/([^}]+function {$method}[^{]+?)\\n\\s+?{/", $code, $matches)
? array_filter(explode("\n", $matches[1]))
: null;
} catch (ReflectionException $ex) {
return null;
}
}
}
53 changes: 34 additions & 19 deletions src/ExpressionLanguageWithTplStr.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,42 @@

namespace uuf6429\ExpressionLanguage;

use Throwable;
use Symfony\Component\ExpressionLanguage\Expression as SymfonyExpression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage as SymfonyExpressionLanguage;
use Symfony\Component\ExpressionLanguage\ParsedExpression as SymfonyParsedExpression;

$instantiable = static function ($class) {
try {
new $class();
return true;
} catch (Throwable $ex) {
return false;
}
};
ClassBuilder::create()
->import(SymfonyExpression::class)
->import(SymfonyParsedExpression::class)
->class(ExpressionLanguageWithTplStr::class)
->extend(SymfonyExpressionLanguage::class)
->use(TemplateStringTranslatorTrait::class)
->override(
'compile',
'return parent::compile($this->convertExpression($expression), $names);'
)
->override(
'evaluate',
'return parent::evaluate($this->convertExpression($expression), $values);'
)
->override(
'parse',
'return parent::parse($this->convertExpression($expression), $names);'
)
->override(
'lint',
'parent::lint($this->convertExpression($expression), $names);'
)
->createClass();

if ($instantiable(Shims\ExpressionLanguageWithTplStrSF6::class)) {
class ExpressionLanguageWithTplStr extends Shims\ExpressionLanguageWithTplStrSF6
{
}
} elseif ($instantiable(Shims\ExpressionLanguageWithTplStrSF5::class)) {
class ExpressionLanguageWithTplStr extends Shims\ExpressionLanguageWithTplStrSF5
{
}
} elseif ($instantiable(Shims\ExpressionLanguageWithTplStrSF4::class)) {
class ExpressionLanguageWithTplStr extends Shims\ExpressionLanguageWithTplStrSF4
/**
* The class definition below will probably never be executed since the real one is generated by
* the ClassBuilder above. However, the code below is still useful to enable IDE autocompletion.
* @codeCoverageIgnore
*/
if (!class_exists(ExpressionLanguageWithTplStr::class)) {
class ExpressionLanguageWithTplStr extends SymfonyExpressionLanguage
{
use TemplateStringTranslatorTrait;
}
}
Loading

0 comments on commit 706ad25

Please sign in to comment.