Skip to content

Commit 936c406

Browse files
authored
Support files[] autoloading (#173)
* Support files[] autoloading * cache files as container parameter * read files from legacy info file * check if parameter for file autoload exists
1 parent 8db5cad commit 936c406

File tree

13 files changed

+236
-1
lines changed

13 files changed

+236
-1
lines changed

bootstrap.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,23 @@
55
use Retrofit\Drupal\Provider;
66

77
$GLOBALS['conf']['container_service_providers']['retrofit'] = Provider::class;
8+
9+
10+
spl_autoload_register(function (string $item) {
11+
if (!\Drupal::hasContainer()) {
12+
return null;
13+
}
14+
static $files;
15+
if ($files === null) {
16+
if (!\Drupal::getContainer()->hasParameter('files_autoload_registry')) {
17+
$files = [];
18+
} else {
19+
$files = \Drupal::getContainer()->getParameter('files_autoload_registry');
20+
}
21+
}
22+
if (isset($files[$item])) {
23+
include $files[$item];
24+
return true;
25+
}
26+
return null;
27+
});

phpstan-baseline.neon

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,16 @@ parameters:
755755
count: 1
756756
path: src/functions/common.php
757757

758+
-
759+
message: "#^Function drupal_parse_info_format\\(\\) has parameter \\$data with no type specified\\.$#"
760+
count: 1
761+
path: src/functions/common.php
762+
763+
-
764+
message: "#^Function drupal_parse_info_format\\(\\) return type has no value type specified in iterable type array\\.$#"
765+
count: 1
766+
path: src/functions/common.php
767+
758768
-
759769
message: "#^Function drupal_render_children\\(\\) has @param\\-out PHPDoc tag for parameter \\$element with no value type specified in iterable type array\\.$#"
760770
count: 1
@@ -795,6 +805,41 @@ parameters:
795805
count: 1
796806
path: src/functions/common.php
797807

808+
-
809+
message: "#^Offset mixed on array\\{\\} in isset\\(\\) does not exist\\.$#"
810+
count: 1
811+
path: src/functions/common.php
812+
813+
-
814+
message: "#^Parameter \\#1 \\$array of function array_pop expects array, array\\<int, string\\>\\|false given\\.$#"
815+
count: 1
816+
path: src/functions/common.php
817+
818+
-
819+
message: "#^Result of \\|\\| is always true\\.$#"
820+
count: 1
821+
path: src/functions/common.php
822+
823+
-
824+
message: "#^Undefined variable\\: \\$value1$#"
825+
count: 1
826+
path: src/functions/common.php
827+
828+
-
829+
message: "#^Undefined variable\\: \\$value2$#"
830+
count: 1
831+
path: src/functions/common.php
832+
833+
-
834+
message: "#^Undefined variable\\: \\$value3$#"
835+
count: 1
836+
path: src/functions/common.php
837+
838+
-
839+
message: "#^Variable \\$key might not be defined\\.$#"
840+
count: 1
841+
path: src/functions/common.php
842+
798843
-
799844
message: "#^Function db_and\\(\\) has no return type specified\\.$#"
800845
count: 1
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Retrofit\Drupal\DependencyInjection\Compiler;
6+
7+
use Drupal\Core\Extension\InfoParser;
8+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
9+
use Symfony\Component\DependencyInjection\ContainerBuilder;
10+
11+
final class FilesAutoloaderPass implements CompilerPassInterface
12+
{
13+
public function process(ContainerBuilder $container): void
14+
{
15+
$filesAutoloadRegistry = [];
16+
$appRoot = $container->getParameter('app.root');
17+
assert(is_string($appRoot) || is_null($appRoot));
18+
$infoParser = new InfoParser($appRoot);
19+
/** @var array<string, array{pathname: string}> $container_modules */
20+
$container_modules = $container->getParameter('container.modules');
21+
$files = [];
22+
foreach ($container_modules as $name => $module) {
23+
$modulePath = dirname($module['pathname']);
24+
$info = $infoParser->parse($module['pathname']);
25+
$files[] = array_map(
26+
static fn(string $file) => "$modulePath/$file",
27+
$info['files'] ?? []
28+
);
29+
30+
$legacyInfoFilePath = "$modulePath/$name.info";
31+
if (file_exists($legacyInfoFilePath)) {
32+
$legacyInfoFile = file_get_contents($legacyInfoFilePath);
33+
if ($legacyInfoFile !== false) {
34+
$legacyInfo = drupal_parse_info_format($legacyInfoFile);
35+
$files[] = array_map(
36+
static fn(string $file) => "$modulePath/$file",
37+
$legacyInfo['files'] ?? []
38+
);
39+
}
40+
}
41+
}
42+
$files = array_unique(array_merge(...$files));
43+
foreach ($files as $file) {
44+
$matches = [];
45+
$contents = file_get_contents($file);
46+
if ($contents === false) {
47+
continue;
48+
}
49+
$result = preg_match_all(
50+
'/^\s*(?:abstract|final)?\s*(class|interface|trait)\s+([a-zA-Z0-9_]+)/m',
51+
$contents,
52+
$matches
53+
);
54+
if ($result !== false) {
55+
foreach ($matches[2] as $itemName) {
56+
$filesAutoloadRegistry[$itemName] = $file;
57+
}
58+
}
59+
}
60+
$container->setParameter('files_autoload_registry', $filesAutoloadRegistry);
61+
}
62+
}

src/Provider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Retrofit\Drupal\Asset\RetrofitJsCollectionRenderer;
1111
use Retrofit\Drupal\Asset\RetrofitLibraryDiscovery;
1212
use Retrofit\Drupal\Controller\RetrofitTitleResolver;
13+
use Retrofit\Drupal\DependencyInjection\Compiler\FilesAutoloaderPass;
1314
use Retrofit\Drupal\Entity\EntityTypeManager;
1415
use Retrofit\Drupal\EventSubscriber\HookExit;
1516
use Retrofit\Drupal\EventSubscriber\HookInit;
@@ -179,6 +180,8 @@ public function register(ContainerBuilder $container)
179180
->addArgument(new Reference('state'))
180181
->setAutowired(true)
181182
->addTag('event_subscriber');
183+
184+
$container->addCompilerPass(new FilesAutoloaderPass());
182185
}
183186

184187
public function alter(ContainerBuilder $container)

src/functions/common.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,3 +540,62 @@ function drupal_write_record(string $table, array|object &$record, array|string
540540

541541
return $return;
542542
}
543+
544+
function drupal_parse_info_format($data): array
545+
{
546+
$info = [];
547+
548+
if (
549+
preg_match_all('
550+
@^\s* # Start at the beginning of a line, ignoring leading whitespace
551+
((?:
552+
[^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets,
553+
\[[^\[\]]*\] # unless they are balanced and not nested
554+
)+?)
555+
\s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space)
556+
(?:
557+
("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes
558+
(\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
559+
([^\r\n]*?) # Non-quoted string
560+
)\s*$ # Stop at the next end of a line, ignoring trailing whitespace
561+
@msx', $data, $matches, PREG_SET_ORDER)
562+
) {
563+
foreach ($matches as $match) {
564+
// Fetch the key and value string.
565+
$i = 0;
566+
foreach (['key', 'value1', 'value2', 'value3'] as $var) {
567+
$$var = isset($match[++$i]) ? $match[$i] : '';
568+
}
569+
$value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;
570+
571+
// Parse array syntax.
572+
$keys = preg_split('/\]?\[/', rtrim($key, ']'));
573+
$last = array_pop($keys);
574+
$parent = &$info;
575+
576+
// Create nested arrays.
577+
foreach ($keys as $key) {
578+
if ($key == '') {
579+
$key = count($parent);
580+
}
581+
if (!isset($parent[$key]) || !is_array($parent[$key])) {
582+
$parent[$key] = array();
583+
}
584+
$parent = &$parent[$key];
585+
}
586+
587+
// Handle PHP constants.
588+
if (preg_match('/^\w+$/i', $value) && defined($value)) {
589+
$value = constant($value);
590+
}
591+
592+
// Insert actual value.
593+
if ($last == '') {
594+
$last = count($parent);
595+
}
596+
$parent[$last] = $value;
597+
}
598+
}
599+
600+
return $info;
601+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name: node_example
2+
type: module
3+
core_version_requirement: '*'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class FooBar {
4+
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name = Node example
2+
description = Demonstrates a custom content type and uses the field api.
3+
package = Example modules
4+
core = 7.x
5+
files[] = foobar.inc

tests/data/retrofit_fixtures/retrfofit_fixtures.info.yml renamed to tests/data/retrofit_fixtures/retrofit_fixtures.info.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ description: Fixtures not present in Examples modules
33
package: Example modules
44
type: module
55
core_version_requirement: ^10
6+
files:
7+
- some_class.inc
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class SomeClass {
4+
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name: theming_example
2+
type: module
3+
core_version_requirement: '*'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Retrofit\Drupal\Tests\Integration;
6+
7+
final class FileClassAutoloaderTest extends IntegrationTestCase
8+
{
9+
protected static $modules = [
10+
'system',
11+
];
12+
13+
protected static function getTestModules(): array
14+
{
15+
return ['retrofit_fixtures'];
16+
}
17+
18+
public function testAutoload(): void
19+
{
20+
self::assertTrue(class_exists(\SomeClass::class));
21+
self::assertTrue(class_exists(\FooBar::class));
22+
}
23+
}

tests/src/Integration/IntegrationTestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function register(ContainerBuilder $container): void
2929
foreach (static::getTestModules() as $module) {
3030
$modules[$module] = [
3131
'type' => 'module',
32-
'pathname' => "../../tests/data/$module/$module.module",
32+
'pathname' => "../../tests/data/$module/$module.info.yml",
3333
'filename' => "$module.module",
3434
];
3535
}

0 commit comments

Comments
 (0)