-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c7a1a44
commit fa2c9b9
Showing
5 changed files
with
174 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Lmc\Cqrs\Bundle\Service; | ||
|
||
use Twig\Extension\AbstractExtension; | ||
use Twig\TwigFilter; | ||
|
||
class ClassExtension extends AbstractExtension | ||
{ | ||
public function getFilters() | ||
{ | ||
return [ | ||
new TwigFilter( | ||
'genericClass', | ||
fn (string $class) => $this->formatGenericClass($class), | ||
['is_safe' => ['html']] | ||
), | ||
]; | ||
} | ||
|
||
private function formatGenericClass(string $class): string | ||
{ | ||
if (empty($class)) { | ||
return $class; | ||
} | ||
|
||
try { | ||
if ($this->tryParseClassWithoutGenerics($class, $shortName)) { | ||
return sprintf( | ||
'%s<strong>%s</strong>', | ||
$this->replaceOnceFromEnd($shortName, '', $class), | ||
$shortName | ||
); | ||
} elseif ($this->tryParseClassWithGenerics($class, $shortName, $genericArguments)) { | ||
if ($this->tryParseClassWithGenerics($genericArguments)) { | ||
$generics = $this->formatGenericClass($genericArguments); | ||
} else { | ||
$generics = array_map( | ||
fn (string $genericArgument) => $this->formatGenericClass(trim($genericArgument)), | ||
explode(',', $genericArguments) | ||
); | ||
|
||
$generics = implode(', ', $generics); | ||
} | ||
|
||
[$classWithoutGenerics] = explode('<', $class, 2); | ||
|
||
return sprintf( | ||
'%s<strong>%s</strong><%s>', | ||
$this->replaceOnceFromEnd($shortName, '', $classWithoutGenerics), | ||
$shortName, | ||
$generics, | ||
); | ||
} | ||
|
||
return $class; | ||
} catch (\Throwable $e) { | ||
return $class; | ||
} | ||
} | ||
|
||
private function replaceOnceFromEnd(string $search, string $replace, string $value): string | ||
{ | ||
$position = mb_strrpos($value, $search); | ||
if ($position === false) { | ||
return $value; | ||
} | ||
|
||
return substr_replace($value, $replace, $position, mb_strlen($search)); | ||
} | ||
|
||
private function tryParseClassWithoutGenerics(string $class, ?string &$shortClassName = null): bool | ||
{ | ||
if (preg_match('/^([A-Z][A-Za-z0-9]*\\\\)*([A-Z][A-Za-z]*?)$/', $class, $matches) === 1) { | ||
$shortClassName = array_pop($matches); | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private function tryParseClassWithGenerics( | ||
string $class, | ||
?string &$shortClassName = null, | ||
?string &$genericArguments = null | ||
): bool { | ||
if (preg_match('/^([A-Z][A-Za-z0-9]*\\\\)*([A-Z][A-Za-z]*?)<(.*)>$/', $class, $matches) === 1) { | ||
$genericArguments = array_pop($matches); | ||
$shortClassName = array_pop($matches); | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Lmc\Cqrs\Bundle\Service; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
|
||
class ClassExtensionTest extends TestCase | ||
{ | ||
private ClassExtension $extension; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->extension = new ClassExtension(); | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider provideClass | ||
*/ | ||
public function shouldFormatClassString(string $string, string $expected): void | ||
{ | ||
$filter = $this->extension->getFilters()[0]; | ||
$this->assertSame('genericClass', $filter->getName()); | ||
|
||
$result = call_user_func($filter->getCallable(), $string); | ||
|
||
$this->assertSame($expected, $result); | ||
} | ||
|
||
public function provideClass(): array | ||
{ | ||
return [ | ||
// input, expected | ||
'empty' => ['', ''], | ||
'not a class' => ['foo', 'foo'], | ||
'class without generics' => ['Root\Service\ServiceName', 'Root\Service\<strong>ServiceName</strong>'], | ||
'class with generic parameter' => [ | ||
'Root\Service\Generic\ServiceName<T>', | ||
'Root\Service\Generic\<strong>ServiceName</strong><<strong>T</strong>>', | ||
], | ||
'generic class' => [ | ||
'Root\Service\Generic\ServiceName<Root\Value\Foo>', | ||
'Root\Service\Generic\<strong>ServiceName</strong><Root\Value\<strong>Foo</strong>>', | ||
], | ||
'generic class with multiple generic arguments' => [ | ||
'Root\Service\Generic\ServiceName<Root\Value\Foo, Root\Value\Bar>', | ||
'Root\Service\Generic\<strong>ServiceName</strong><Root\Value\<strong>Foo</strong>, Root\Value\<strong>Bar</strong>>', | ||
], | ||
'generic class with generic class argument' => [ | ||
'Root\Service\Generic\ServiceName<Root\Value\Foo<Root\Value\Bar>>', | ||
'Root\Service\Generic\<strong>ServiceName</strong><Root\Value\<strong>Foo</strong><Root\Value\<strong>Bar</strong>>>', | ||
], | ||
'generic class with duplicity in name' => [ | ||
'Root\Service\Generic\ServiceName<Root\Value\Foo\Foo>', | ||
'Root\Service\Generic\<strong>ServiceName</strong><Root\Value\Foo\<strong>Foo</strong>>', | ||
], | ||
'generic class with multiple duplicities' => [ | ||
'Root\Service\Generic\Foo<Root\Value\Foo<Root\Foo\Foo, Root\Foo\Foo>>', | ||
'Root\Service\Generic\<strong>Foo</strong><Root\Value\<strong>Foo</strong><Root\Foo\<strong>Foo</strong>, Root\Foo\<strong>Foo</strong>>>', | ||
], | ||
]; | ||
} | ||
} |