NEW: Add support for autoloading PHP 8.1 enums

This commit is contained in:
Loz Calver 2022-07-19 17:02:20 +01:00 committed by Loz Calver
parent c0e8a21acf
commit d3f104382d
6 changed files with 128 additions and 13 deletions

View File

@ -6,7 +6,7 @@ namespace SilverStripe\Core\Manifest;
* Class ClassContentRemover * Class ClassContentRemover
* @package SilverStripe\Core\Manifest * @package SilverStripe\Core\Manifest
* *
* A utility class to clean the contents of a PHP file containing classes. * A utility class to clean the contents of a PHP file containing classes/interfaces/traits/enums.
* *
* It removes any code with in `$cut_off_depth` number of curly braces. * It removes any code with in `$cut_off_depth` number of curly braces.
*/ */
@ -29,7 +29,7 @@ class ClassContentRemover
return $contents; return $contents;
} }
if (!preg_match('/\b(?:class|interface|trait)/i', $contents ?? '')) { if (!preg_match('/\b(?:class|interface|trait|enum)/i', $contents ?? '')) {
return ''; return '';
} }
@ -50,7 +50,12 @@ class ClassContentRemover
} }
// only count if we see a class/interface/trait keyword // only count if we see a class/interface/trait keyword
if (!$startCounting && in_array($token[0], [T_CLASS, T_INTERFACE, T_TRAIT])) { $targetTokens = [T_CLASS, T_INTERFACE, T_TRAIT];
if (version_compare(phpversion(), '8.1.0', '>')) {
$targetTokens[] = T_ENUM;
}
if (!$startCounting && in_array($token[0], $targetTokens)) {
$startCounting = true; $startCounting = true;
} }

View File

@ -67,6 +67,8 @@ class ClassManifest
'implementors', 'implementors',
'traits', 'traits',
'traitNames', 'traitNames',
'enums',
'enumNames',
]; ];
/** /**
@ -150,6 +152,20 @@ class ClassManifest
*/ */
protected $traitNames = []; protected $traitNames = [];
/**
* Map of lowercase enum names to paths
*
* @var array
*/
protected $enums = [];
/**
* Map of lowercase enum names to proper case
*
* @var array
*/
protected $enumNames = [];
/** /**
* PHP Parser for parsing found files * PHP Parser for parsing found files
* *
@ -334,10 +350,11 @@ class ClassManifest
{ {
$lowerName = strtolower($name ?? ''); $lowerName = strtolower($name ?? '');
foreach ([ foreach ([
$this->classes, $this->classes,
$this->interfaces, $this->interfaces,
$this->traits, $this->traits,
] as $source) { $this->enums,
] as $source) {
if (isset($source[$lowerName]) && file_exists($source[$lowerName] ?? '')) { if (isset($source[$lowerName]) && file_exists($source[$lowerName] ?? '')) {
return $source[$lowerName]; return $source[$lowerName];
} }
@ -355,10 +372,11 @@ class ClassManifest
{ {
$lowerName = strtolower($name ?? ''); $lowerName = strtolower($name ?? '');
foreach ([ foreach ([
$this->classNames, $this->classNames,
$this->interfaceNames, $this->interfaceNames,
$this->traitNames, $this->traitNames,
] as $source) { $this->enumNames,
] as $source) {
if (isset($source[$lowerName])) { if (isset($source[$lowerName])) {
return $source[$lowerName]; return $source[$lowerName];
} }
@ -406,6 +424,26 @@ class ClassManifest
return $this->traitNames; return $this->traitNames;
} }
/**
* Returns a map of lowercased enum names to file paths.
*
* @return array
*/
public function getEnums()
{
return $this->enums;
}
/**
* Returns a map of lowercase enum names to proper enum names in the manifest
*
* @return array
*/
public function getEnumNames()
{
return $this->enumNames;
}
/** /**
* Returns an array of all the descendant data. * Returns an array of all the descendant data.
* *
@ -562,6 +600,7 @@ class ClassManifest
$classes = $data['classes']; $classes = $data['classes'];
$interfaces = $data['interfaces']; $interfaces = $data['interfaces'];
$traits = $data['traits']; $traits = $data['traits'];
$enums = $data['enums'];
} else { } else {
$changed = true; $changed = true;
// Build from php file parser // Build from php file parser
@ -579,6 +618,7 @@ class ClassManifest
$classes = $this->getVisitor()->getClasses(); $classes = $this->getVisitor()->getClasses();
$interfaces = $this->getVisitor()->getInterfaces(); $interfaces = $this->getVisitor()->getInterfaces();
$traits = $this->getVisitor()->getTraits(); $traits = $this->getVisitor()->getTraits();
$enums = $this->getVisitor()->getEnums();
} }
// Merge raw class data into global list // Merge raw class data into global list
@ -641,6 +681,13 @@ class ClassManifest
$this->traitNames[$lowerTrait] = $traitName; $this->traitNames[$lowerTrait] = $traitName;
} }
// Merge all enums
foreach ($enums as $enumName => $enumInfo) {
$lowerEnum = strtolower($enumName ?? '');
$this->enums[$lowerEnum] = $pathname;
$this->enumNames[$lowerEnum] = $enumName;
}
// Save back to cache if configured // Save back to cache if configured
if ($changed && $this->cache) { if ($changed && $this->cache) {
$cache = [ $cache = [
@ -725,7 +772,7 @@ class ClassManifest
if (!$data || !is_array($data)) { if (!$data || !is_array($data)) {
return false; return false;
} }
foreach (['classes', 'interfaces', 'traits'] as $key) { foreach (['classes', 'interfaces', 'traits', 'enums'] as $key) {
// Must be set // Must be set
if (!isset($data[$key])) { if (!isset($data[$key])) {
return false; return false;

View File

@ -15,10 +15,20 @@ class ClassManifestVisitor extends NodeVisitorAbstract
private $interfaces = []; private $interfaces = [];
private bool $includeEnums;
private $enums = [];
public function __construct()
{
$this->includeEnums = version_compare(phpversion(), '8.1.0', '>');
}
public function resetState() public function resetState()
{ {
$this->classes = []; $this->classes = [];
$this->traits = []; $this->traits = [];
$this->enums = [];
$this->interfaces = []; $this->interfaces = [];
} }
@ -49,6 +59,8 @@ class ClassManifestVisitor extends NodeVisitorAbstract
]; ];
} elseif ($node instanceof Node\Stmt\Trait_) { } elseif ($node instanceof Node\Stmt\Trait_) {
$this->traits[(string)$node->namespacedName] = []; $this->traits[(string)$node->namespacedName] = [];
} elseif ($this->includeEnums && $node instanceof Node\Stmt\Enum_) {
$this->enums[(string)$node->namespacedName] = [];
} elseif ($node instanceof Node\Stmt\Interface_) { } elseif ($node instanceof Node\Stmt\Interface_) {
$extends = []; $extends = [];
foreach ($node->extends as $ancestor) { foreach ($node->extends as $ancestor) {
@ -74,6 +86,11 @@ class ClassManifestVisitor extends NodeVisitorAbstract
return $this->traits; return $this->traits;
} }
public function getEnums()
{
return $this->enums;
}
public function getInterfaces() public function getInterfaces()
{ {
return $this->interfaces; return $this->interfaces;

View File

@ -43,7 +43,7 @@ class ClassManifestTest extends SapphireTest
*/ */
public function providerTestGetItemPath() public function providerTestGetItemPath()
{ {
return [ $paths = [
['CLASSA', 'module/classes/ClassA.php'], ['CLASSA', 'module/classes/ClassA.php'],
['ClassA', 'module/classes/ClassA.php'], ['ClassA', 'module/classes/ClassA.php'],
['classa', 'module/classes/ClassA.php'], ['classa', 'module/classes/ClassA.php'],
@ -55,6 +55,14 @@ class ClassManifestTest extends SapphireTest
['VendorClassA', 'vendor/silverstripe/modulec/code/VendorClassA.php'], ['VendorClassA', 'vendor/silverstripe/modulec/code/VendorClassA.php'],
['VendorTraitA', 'vendor/silverstripe/modulec/code/VendorTraitA.php'], ['VendorTraitA', 'vendor/silverstripe/modulec/code/VendorTraitA.php'],
]; ];
if (version_compare(phpversion(), '8.1.0', '>')) {
$paths[] = ['ENUMA', 'module/enums/EnumA.php'];
$paths[] = ['EnumA', 'module/enums/EnumA.php'];
$paths[] = ['enuma', 'module/enums/EnumA.php'];
}
return $paths;
} }
/** /**
@ -169,6 +177,34 @@ class ClassManifestTest extends SapphireTest
} }
} }
public function testGetEnums()
{
if (!version_compare(phpversion(), '8.1.0', '>')) {
$this->markTestSkipped('Enums are only available on PHP 8.1+');
}
$expect = [
'enuma' => "{$this->base}/module/enums/EnumA.php",
'enumb' => "{$this->base}/module/enums/EnumB.php",
];
$this->assertEquals($expect, $this->manifest->getEnums());
}
public function testGetEnumNames()
{
if (!version_compare(phpversion(), '8.1.0', '>')) {
$this->markTestSkipped('Enums are only available on PHP 8.1+');
}
$this->assertEquals(
[
'enuma' => 'EnumA',
'enumb' => 'EnumB',
],
$this->manifest->getEnumNames()
);
}
public function testTestManifestIncludesTestClasses() public function testTestManifestIncludesTestClasses()
{ {
$this->assertArrayNotHasKey('testclassa', $this->manifest->getClasses()); $this->assertArrayNotHasKey('testclassa', $this->manifest->getClasses());

View File

@ -0,0 +1,5 @@
<?php
/**
* @ignore
*/
enum EnumA { }

View File

@ -0,0 +1,5 @@
<?php
/**
* @ignore
*/
enum EnumB { }