Skip to content

Commit

Permalink
Support files[] autoloading (#173)
Browse files Browse the repository at this point in the history
* Support files[] autoloading

* cache files as container parameter

* read files from legacy info file

* check if parameter for file autoload exists
  • Loading branch information
mglaman authored May 15, 2024
1 parent 8db5cad commit 936c406
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 1 deletion.
20 changes: 20 additions & 0 deletions bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,23 @@
use Retrofit\Drupal\Provider;

$GLOBALS['conf']['container_service_providers']['retrofit'] = Provider::class;


spl_autoload_register(function (string $item) {
if (!\Drupal::hasContainer()) {
return null;
}
static $files;
if ($files === null) {
if (!\Drupal::getContainer()->hasParameter('files_autoload_registry')) {
$files = [];
} else {
$files = \Drupal::getContainer()->getParameter('files_autoload_registry');
}
}
if (isset($files[$item])) {
include $files[$item];
return true;
}
return null;
});
45 changes: 45 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,16 @@ parameters:
count: 1
path: src/functions/common.php

-
message: "#^Function drupal_parse_info_format\\(\\) has parameter \\$data with no type specified\\.$#"
count: 1
path: src/functions/common.php

-
message: "#^Function drupal_parse_info_format\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/functions/common.php

-
message: "#^Function drupal_render_children\\(\\) has @param\\-out PHPDoc tag for parameter \\$element with no value type specified in iterable type array\\.$#"
count: 1
Expand Down Expand Up @@ -795,6 +805,41 @@ parameters:
count: 1
path: src/functions/common.php

-
message: "#^Offset mixed on array\\{\\} in isset\\(\\) does not exist\\.$#"
count: 1
path: src/functions/common.php

-
message: "#^Parameter \\#1 \\$array of function array_pop expects array, array\\<int, string\\>\\|false given\\.$#"
count: 1
path: src/functions/common.php

-
message: "#^Result of \\|\\| is always true\\.$#"
count: 1
path: src/functions/common.php

-
message: "#^Undefined variable\\: \\$value1$#"
count: 1
path: src/functions/common.php

-
message: "#^Undefined variable\\: \\$value2$#"
count: 1
path: src/functions/common.php

-
message: "#^Undefined variable\\: \\$value3$#"
count: 1
path: src/functions/common.php

-
message: "#^Variable \\$key might not be defined\\.$#"
count: 1
path: src/functions/common.php

-
message: "#^Function db_and\\(\\) has no return type specified\\.$#"
count: 1
Expand Down
62 changes: 62 additions & 0 deletions src/DependencyInjection/Compiler/FilesAutoloaderPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Retrofit\Drupal\DependencyInjection\Compiler;

use Drupal\Core\Extension\InfoParser;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class FilesAutoloaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$filesAutoloadRegistry = [];
$appRoot = $container->getParameter('app.root');
assert(is_string($appRoot) || is_null($appRoot));
$infoParser = new InfoParser($appRoot);
/** @var array<string, array{pathname: string}> $container_modules */
$container_modules = $container->getParameter('container.modules');
$files = [];
foreach ($container_modules as $name => $module) {
$modulePath = dirname($module['pathname']);
$info = $infoParser->parse($module['pathname']);
$files[] = array_map(
static fn(string $file) => "$modulePath/$file",
$info['files'] ?? []
);

$legacyInfoFilePath = "$modulePath/$name.info";
if (file_exists($legacyInfoFilePath)) {
$legacyInfoFile = file_get_contents($legacyInfoFilePath);
if ($legacyInfoFile !== false) {
$legacyInfo = drupal_parse_info_format($legacyInfoFile);
$files[] = array_map(
static fn(string $file) => "$modulePath/$file",
$legacyInfo['files'] ?? []
);
}
}
}
$files = array_unique(array_merge(...$files));
foreach ($files as $file) {
$matches = [];
$contents = file_get_contents($file);
if ($contents === false) {
continue;
}
$result = preg_match_all(
'/^\s*(?:abstract|final)?\s*(class|interface|trait)\s+([a-zA-Z0-9_]+)/m',
$contents,
$matches
);
if ($result !== false) {
foreach ($matches[2] as $itemName) {
$filesAutoloadRegistry[$itemName] = $file;
}
}
}
$container->setParameter('files_autoload_registry', $filesAutoloadRegistry);
}
}
3 changes: 3 additions & 0 deletions src/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Retrofit\Drupal\Asset\RetrofitJsCollectionRenderer;
use Retrofit\Drupal\Asset\RetrofitLibraryDiscovery;
use Retrofit\Drupal\Controller\RetrofitTitleResolver;
use Retrofit\Drupal\DependencyInjection\Compiler\FilesAutoloaderPass;
use Retrofit\Drupal\Entity\EntityTypeManager;
use Retrofit\Drupal\EventSubscriber\HookExit;
use Retrofit\Drupal\EventSubscriber\HookInit;
Expand Down Expand Up @@ -179,6 +180,8 @@ public function register(ContainerBuilder $container)
->addArgument(new Reference('state'))
->setAutowired(true)
->addTag('event_subscriber');

$container->addCompilerPass(new FilesAutoloaderPass());
}

public function alter(ContainerBuilder $container)
Expand Down
59 changes: 59 additions & 0 deletions src/functions/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -540,3 +540,62 @@ function drupal_write_record(string $table, array|object &$record, array|string

return $return;
}

function drupal_parse_info_format($data): array
{
$info = [];

if (
preg_match_all('
@^\s* # Start at the beginning of a line, ignoring leading whitespace
((?:
[^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets,
\[[^\[\]]*\] # unless they are balanced and not nested
)+?)
\s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space)
(?:
("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes
(\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
([^\r\n]*?) # Non-quoted string
)\s*$ # Stop at the next end of a line, ignoring trailing whitespace
@msx', $data, $matches, PREG_SET_ORDER)
) {
foreach ($matches as $match) {
// Fetch the key and value string.
$i = 0;
foreach (['key', 'value1', 'value2', 'value3'] as $var) {
$$var = isset($match[++$i]) ? $match[$i] : '';
}
$value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;

// Parse array syntax.
$keys = preg_split('/\]?\[/', rtrim($key, ']'));
$last = array_pop($keys);
$parent = &$info;

// Create nested arrays.
foreach ($keys as $key) {
if ($key == '') {
$key = count($parent);
}
if (!isset($parent[$key]) || !is_array($parent[$key])) {
$parent[$key] = array();
}
$parent = &$parent[$key];
}

// Handle PHP constants.
if (preg_match('/^\w+$/i', $value) && defined($value)) {
$value = constant($value);
}

// Insert actual value.
if ($last == '') {
$last = count($parent);
}
$parent[$last] = $value;
}
}

return $info;
}
3 changes: 3 additions & 0 deletions tests/data/node_example/node_example.info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: node_example
type: module
core_version_requirement: '*'
5 changes: 5 additions & 0 deletions tests/data/retrofit_fixtures/foobar.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

class FooBar {

}
5 changes: 5 additions & 0 deletions tests/data/retrofit_fixtures/retrofit_fixtures.info
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name = Node example
description = Demonstrates a custom content type and uses the field api.
package = Example modules
core = 7.x
files[] = foobar.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ description: Fixtures not present in Examples modules
package: Example modules
type: module
core_version_requirement: ^10
files:
- some_class.inc
5 changes: 5 additions & 0 deletions tests/data/retrofit_fixtures/some_class.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

class SomeClass {

}
3 changes: 3 additions & 0 deletions tests/data/theming_example/theming_example.info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: theming_example
type: module
core_version_requirement: '*'
23 changes: 23 additions & 0 deletions tests/src/Integration/FileClassAutoloaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Retrofit\Drupal\Tests\Integration;

final class FileClassAutoloaderTest extends IntegrationTestCase
{
protected static $modules = [
'system',
];

protected static function getTestModules(): array
{
return ['retrofit_fixtures'];
}

public function testAutoload(): void
{
self::assertTrue(class_exists(\SomeClass::class));
self::assertTrue(class_exists(\FooBar::class));
}
}
2 changes: 1 addition & 1 deletion tests/src/Integration/IntegrationTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function register(ContainerBuilder $container): void
foreach (static::getTestModules() as $module) {
$modules[$module] = [
'type' => 'module',
'pathname' => "../../tests/data/$module/$module.module",
'pathname' => "../../tests/data/$module/$module.info.yml",
'filename' => "$module.module",
];
}
Expand Down

0 comments on commit 936c406

Please sign in to comment.