Skip to content

Commit 055fb4e

Browse files
Merge pull request #11 from emulsify-ds/codex/php84-compat-tests
Codex/php84 compat tests
2 parents 1c7815c + 5608b31 commit 055fb4e

10 files changed

Lines changed: 462 additions & 21 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: PHP Compatibility
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
- "*.x"
9+
10+
jobs:
11+
lint:
12+
name: PHP ${{ matrix.php-version }} lint
13+
runs-on: ubuntu-latest
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
php-version:
18+
- "8.4"
19+
- "8.5"
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Setup PHP
25+
uses: shivammathur/setup-php@v2
26+
with:
27+
php-version: ${{ matrix.php-version }}
28+
coverage: none
29+
30+
- name: Lint PHP sources
31+
run: git ls-files '*.php' | xargs -n 1 php -l

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ This module provides Emulsify Twig extensions, theme-defined Twig namespaces, an
44

55
## Compatibility
66

7-
This module now targets Drupal `11.3+`.
7+
This module now targets Drupal `11.3+` and PHP `8.4+`.
88

9-
The bundled Drush command follows the Drush 13+ autowiring pattern.
9+
The bundled Drush command follows the Drush 13+ autowiring pattern, and the
10+
codebase now uses PHP 8.4-only syntax where it improves readability.
1011

1112
## Usage
1213

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"homepage": "https://emulsify.info",
66
"license": "GPL-2.0-only",
77
"require": {
8-
"php": ">=8.3",
8+
"php": "^8.4",
99
"drupal/core": "^11.3"
1010
},
1111
"conflict": {

src/Drush/Commands/SubThemeCommands.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private function customizeStarterRecipe(string $name, string $machineName, strin
245245
* The finder.
246246
*/
247247
private function getDirectDescendants(string $dir): Finder {
248-
return (new Finder())
248+
return new Finder()
249249
->in($dir)
250250
->depth('== 0');
251251
}

src/SubThemeGenerator.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function generate(string $directory, string $machineName, string $name):
5656
* The original machine name.
5757
*/
5858
private function discoverOriginalMachineName(string $directory): string {
59-
$finder = (new Finder())
59+
$finder = new Finder()
6060
->files()
6161
->depth('== 0')
6262
->in($directory)
@@ -140,7 +140,7 @@ private function modifyFileContent(string $fileName, array $replacementPairs): v
140140
*/
141141
private function getFileNamesToRename(string $directory, string $originalMachineName): array {
142142
$fileNames = [];
143-
$finder = (new Finder())
143+
$finder = new Finder()
144144
->files()
145145
->in($directory)
146146
->name("*{$originalMachineName}*");
@@ -165,7 +165,7 @@ private function getFileNamesToRename(string $directory, string $originalMachine
165165
*/
166166
private function getDirectoryNamesToRename(string $directory, string $originalMachineName): array {
167167
$directoryNames = [];
168-
$finder = (new Finder())
168+
$finder = new Finder()
169169
->directories()
170170
->in($directory)
171171
->name("*{$originalMachineName}*");
@@ -211,7 +211,7 @@ private function getFileContentReplacementPairs(string $machineName, string $nam
211211
*/
212212
private function getFilesToMakeReplacements(string $directory): array {
213213
$fileNames = [];
214-
$finder = (new Finder())
214+
$finder = new Finder()
215215
->files()
216216
->in($directory);
217217

src/Twig/ThemeNamespaceRegistry.php

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ final class ThemeNamespaceRegistry {
5050
*/
5151
private array $warnedNamespaces = [];
5252

53+
/**
54+
* Protected default namespaces keyed by namespace.
55+
*
56+
* @var array<string, array{name: string, type: string}>|null
57+
*/
58+
private ?array $protectedNamespaces = NULL;
59+
5360
/**
5461
* Creates the registry.
5562
*/
@@ -316,18 +323,81 @@ private function resolvePath(Extension $theme, string $path): string {
316323
}
317324

318325
/**
319-
* Returns whether the namespace would shadow a default Drupal namespace.
326+
* Returns protected default namespaces keyed by namespace.
327+
*
328+
* @return array<string, array{name: string, type: string}>
329+
* Protected default namespace owner metadata.
320330
*/
321-
private function isProtectedNamespace(string $namespace, string $definingThemeName): bool {
322-
if ($namespace === $definingThemeName) {
331+
private function getProtectedNamespaces(): array {
332+
return $this->protectedNamespaces ??= $this->buildProtectedNamespaces();
333+
}
334+
335+
/**
336+
* Builds protected default namespaces for installed modules and themes.
337+
*
338+
* Extensions may opt into reuse of their default namespace via
339+
* `components.allow_default_namespace_reuse` or by defining a matching
340+
* default namespace under `components.namespaces`.
341+
*
342+
* @return array<string, array{name: string, type: string}>
343+
* Protected default namespace owner metadata.
344+
*/
345+
private function buildProtectedNamespaces(): array {
346+
$protectedNamespaces = [];
347+
348+
foreach ($this->moduleExtensionList->getList() as $extensionName => $extension) {
349+
if (!$extension instanceof Extension || $this->allowsDefaultNamespaceReuse($extension)) {
350+
continue;
351+
}
352+
353+
$protectedNamespaces[$extensionName] = [
354+
'name' => (string) ($extension->info['name'] ?? $extensionName),
355+
'type' => 'module',
356+
];
357+
}
358+
359+
// Themes win ties to match Drupal's existing namespace precedence.
360+
foreach ($this->themeExtensionList->getList() as $extensionName => $extension) {
361+
if (!$extension instanceof Extension || $this->allowsDefaultNamespaceReuse($extension)) {
362+
continue;
363+
}
364+
365+
$protectedNamespaces[$extensionName] = [
366+
'name' => (string) ($extension->info['name'] ?? $extensionName),
367+
'type' => 'theme',
368+
];
369+
}
370+
371+
return $protectedNamespaces;
372+
}
373+
374+
/**
375+
* Returns whether an extension allows reuse of its default namespace.
376+
*/
377+
private function allowsDefaultNamespaceReuse(Extension $extension): bool {
378+
$components = $extension->info['components'] ?? NULL;
379+
if (!is_array($components)) {
323380
return FALSE;
324381
}
325382

326-
if (isset($this->themeExtensionList->getList()[$namespace])) {
383+
// Mirror drupal/components, where presence of the key opts in.
384+
if (array_key_exists('allow_default_namespace_reuse', $components)) {
327385
return TRUE;
328386
}
329387

330-
return isset($this->moduleExtensionList->getList()[$namespace]);
388+
$definitions = $components['namespaces'] ?? NULL;
389+
return is_array($definitions) && !empty($definitions[$extension->getName()]);
390+
}
391+
392+
/**
393+
* Returns whether the namespace would shadow a protected default namespace.
394+
*/
395+
private function isProtectedNamespace(string $namespace, string $definingThemeName): bool {
396+
if ($namespace === $definingThemeName) {
397+
return FALSE;
398+
}
399+
400+
return isset($this->getProtectedNamespaces()[$namespace]);
331401
}
332402

333403
/**
@@ -375,14 +445,9 @@ private function logMissingPath(string $themeName, string $namespace, string $pa
375445
* The owner type and human-readable name.
376446
*/
377447
private function getProtectedNamespaceOwner(string $namespace): array {
378-
$theme = $this->themeExtensionList->getList()[$namespace] ?? NULL;
379-
if ($theme instanceof Extension) {
380-
return ['theme', (string) ($theme->info['name'] ?? $namespace)];
381-
}
382-
383-
$module = $this->moduleExtensionList->getList()[$namespace] ?? NULL;
384-
if ($module instanceof Extension) {
385-
return ['module', (string) ($module->info['name'] ?? $namespace)];
448+
$owner = $this->getProtectedNamespaces()[$namespace] ?? NULL;
449+
if (is_array($owner)) {
450+
return [$owner['type'], $owner['name']];
386451
}
387452

388453
return ['extension', $namespace];
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Drupal\Tests\emulsify_tools\Unit;
6+
7+
use Drupal\Core\Template\Attribute;
8+
use Drupal\emulsify_tools\BemTwigExtension;
9+
use Drupal\emulsify_tools\TwigAttributeManager;
10+
use Drupal\Tests\UnitTestCase;
11+
12+
/**
13+
* Tests the BEM Twig extension.
14+
*
15+
* @coversDefaultClass \Drupal\emulsify_tools\BemTwigExtension
16+
* @group emulsify_tools
17+
*/
18+
final class BemTwigExtensionTest extends UnitTestCase {
19+
20+
/**
21+
* Tests positional bem() arguments.
22+
*
23+
* @covers ::bem
24+
*/
25+
public function testBemBuildsExpectedClassesFromPositionalArguments(): void {
26+
$extension = new BemTwigExtension(new TwigAttributeManager());
27+
$sourceAttributes = new Attribute([
28+
'class' => ['existing'],
29+
'data-role' => 'heading',
30+
]);
31+
32+
$result = $extension->bem(
33+
['attributes' => $sourceAttributes],
34+
'title',
35+
['small', 'red'],
36+
'card',
37+
['js-click', 'bad value'],
38+
);
39+
40+
$resultArray = $result->toArray();
41+
42+
self::assertSame([
43+
'card__title',
44+
'card__title--small',
45+
'card__title--red',
46+
'js-click',
47+
'bad-value',
48+
'existing',
49+
], $resultArray['class']);
50+
self::assertSame('heading', $resultArray['data-role']);
51+
}
52+
53+
/**
54+
* Tests array-style bem() arguments.
55+
*
56+
* @covers ::bem
57+
*/
58+
public function testBemBuildsExpectedClassesFromConfigurationArray(): void {
59+
$extension = new BemTwigExtension(new TwigAttributeManager());
60+
61+
$result = $extension->bem(
62+
[],
63+
[
64+
'base_class' => 'title',
65+
'modifiers' => 'small',
66+
'blockname' => 'card',
67+
'extra' => ['js-click'],
68+
],
69+
);
70+
71+
self::assertSame([
72+
'card__title',
73+
'card__title--small',
74+
'js-click',
75+
], $result->toArray()['class']);
76+
}
77+
78+
}

0 commit comments

Comments
 (0)