• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CPS-IT / handlebars / 20825914857

08 Jan 2026 05:34PM UTC coverage: 91.533% (+0.6%) from 90.94%
20825914857

Pull #512

github

web-flow
Merge 0b3f3b11b into 2c0e31d3a
Pull Request #512: [FEATURE] Integrate AssetCollector for asset management

126 of 130 new or added lines in 4 files covered. (96.92%)

2 existing lines in 1 file now uncovered.

1200 of 1311 relevant lines covered (91.53%)

6.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.33
/Classes/Service/AssetService.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "handlebars".
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17

18
namespace CPSIT\Typo3Handlebars\Service;
19

20
use CPSIT\Typo3Handlebars\Exception;
21
use CPSIT\Typo3Handlebars\Exception\InvalidAssetConfigurationException;
22
use TYPO3\CMS\Core\Page\AssetCollector;
23

24
/**
25
 * AssetService
26
 *
27
 * Processes asset configuration and registers assets with TYPO3's AssetCollector.
28
 * Supports JavaScript, CSS, and inline variants with full attribute and option support.
29
 *
30
 * @author Vladimir Falcon Piva <v.falcon@familie-redlich.de>
31
 * @license GPL-2.0-or-later
32
 */
33
final readonly class AssetService
34
{
35
    private const KNOWN_OPTIONS = ['priority', 'useNonce'];
36

37
    public function __construct(
32✔
38
        private AssetCollector $assetCollector,
39
    ) {}
32✔
40

41
    /**
42
     * Process and register assets from configuration.
43
     *
44
     * @param array<int|string, mixed> $assetsConfig Plain asset configuration array
45
     * @throws Exception\InvalidAssetConfigurationException
46
     */
47
    public function registerAssets(array $assetsConfig): void
31✔
48
    {
49
        foreach ($assetsConfig as $typeKey => $assets) {
31✔
50
            // Validate and get AssetType (validates everything)
51
            $assetType = $this->validateConfiguration($typeKey, $assets);
30✔
52

53
            // Process validated assets
54
            $this->processAssets($assetType, $assets);
25✔
55
        }
56
    }
57

58
    /**
59
     * @param array<int|string, mixed> $assets
60
     */
61
    private function processAssets(
25✔
62
        AssetType $type,
63
        array $assets,
64
    ): void {
65
        foreach ($assets as $identifier => $assetConfig) {
25✔
66
            $this->processAsset((string)$identifier, $type, $assetConfig);
25✔
67
        }
68
    }
69

70
    /**
71
     * @param array<string, mixed> $assetConfig
72
     */
73
    private function processAsset(
25✔
74
        string $identifier,
75
        AssetType $type,
76
        array $assetConfig,
77
    ): void {
78
        $source = \trim((string)$assetConfig['source']);
25✔
79

80
        $attributes = $this->processAttributes(
25✔
81
            $assetConfig['attributes'] ?? [],
25✔
82
            $type,
25✔
83
        );
25✔
84
        $options = $this->processOptions($assetConfig['options'] ?? []);
25✔
85

86
        $this->registerWithCollector($type, $identifier, $source, $attributes, $options);
25✔
87
    }
88

89
    /**
90
     * Validate complete asset type configuration.
91
     *
92
     * Validates:
93
     * - Asset type key is valid
94
     * - Assets is an array
95
     * - Each identifier is valid (string, not empty)
96
     * - Each asset config is valid (array with source)
97
     *
98
     * @param string|int $typeKey The asset type key from config
99
     * @param mixed $assets The assets configuration for this type
100
     * @return AssetType The validated AssetType enum
101
     * @throws InvalidAssetConfigurationException
102
     */
103
    private function validateConfiguration(
30✔
104
        string|int $typeKey,
105
        mixed $assets,
106
    ): AssetType {
107
        $assetType = AssetType::tryFrom((string)$typeKey);
30✔
108

109
        if ($assetType === null) {
30✔
110
            throw Exception\InvalidAssetConfigurationException::forUnknownAssetType((string)$typeKey);
1✔
111
        }
112

113
        if (!\is_array($assets)) {
29✔
NEW
114
            throw Exception\InvalidAssetConfigurationException::forInvalidAssetsArray($assetType->value);
×
115
        }
116

117
        foreach ($assets as $identifier => $assetConfig) {
29✔
118
            if (!\is_string($identifier) || \trim($identifier) === '') {
29✔
NEW
119
                throw Exception\InvalidAssetConfigurationException::forInvalidIdentifier($assetType->value);
×
120
            }
121

122
            if (!\is_array($assetConfig)) {
29✔
NEW
123
                throw Exception\InvalidAssetConfigurationException::forInvalidConfiguration($identifier, $assetType->value);
×
124
            }
125

126
            if (!isset($assetConfig['source']) || \trim((string)$assetConfig['source']) === '') {
29✔
127
                throw Exception\InvalidAssetConfigurationException::forMissingSource($identifier, $assetType->value);
4✔
128
            }
129
        }
130

131
        return $assetType;
25✔
132
    }
133

134
    /**
135
     * Register asset with AssetCollector using the appropriate method.
136
     *
137
     * @param array<string, string> $attributes
138
     * @param array<string, bool> $options
139
     */
140
    private function registerWithCollector(
25✔
141
        AssetType $type,
142
        string $identifier,
143
        string $source,
144
        array $attributes,
145
        array $options,
146
    ): void {
147
        $method = $type->getCollectorMethod();
25✔
148
        $this->assetCollector->$method($identifier, $source, $attributes, $options);
25✔
149
    }
150

151
    /**
152
     * Process and normalize HTML attributes.
153
     *
154
     * @return array<string, string>
155
     */
156
    private function processAttributes(mixed $attributes, AssetType $type): array
25✔
157
    {
158
        if (!\is_array($attributes)) {
25✔
NEW
159
            return [];
×
160
        }
161

162
        $processed = [];
25✔
163

164
        foreach ($attributes as $name => $value) {
25✔
165
            $name = (string)$name;
12✔
166
            $processed = $this->addAttribute($processed, $name, $value, $type);
12✔
167
        }
168

169
        return $processed;
25✔
170
    }
171

172
    /**
173
     * @param array<string, string> $processed
174
     * @return array<string, string>
175
     */
176
    private function addAttribute(
12✔
177
        array $processed,
178
        string $name,
179
        mixed $value,
180
        AssetType $type,
181
    ): array {
182
        if ($type->isBooleanAttribute($name)) {
12✔
183
            return $this->addBooleanAttribute($processed, $name, $value);
7✔
184
        }
185

186
        return $this->addRegularAttribute($processed, $name, $value);
9✔
187
    }
188

189
    /**
190
     * Add a boolean attribute (e.g., async, defer, disabled).
191
     *
192
     * @param array<string, string> $processed
193
     * @return array<string, string>
194
     */
195
    private function addBooleanAttribute(array $processed, string $name, mixed $value): array
7✔
196
    {
197
        if ((bool)$value === true) {
7✔
198
            $processed[$name] = $name;
7✔
199
        }
200
        return $processed;
7✔
201
    }
202

203
    /**
204
     * @param array<string, string> $processed
205
     * @return array<string, string>
206
     */
207
    private function addRegularAttribute(array $processed, string $name, mixed $value): array
9✔
208
    {
209
        if ($value !== null && $value !== '') {
9✔
210
            $processed[$name] = (string)$value;
9✔
211
        }
212
        return $processed;
9✔
213
    }
214

215
    /**
216
     * Process AssetCollector options.
217
     *
218
     * @param array<string, mixed> $options
219
     * @return array<string, bool>
220
     */
221
    private function processOptions(array $options): array
25✔
222
    {
223
        $processed = [];
25✔
224

225
        foreach (self::KNOWN_OPTIONS as $option) {
25✔
226
            if (isset($options[$option])) {
25✔
227
                $processed[$option] = (bool)$options[$option];
4✔
228
            }
229
        }
230

231
        return $processed;
25✔
232
    }
233
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc