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

tito10047 / progressive-image-bundle / 20675431244

03 Jan 2026 09:29AM UTC coverage: 88.617% (+0.3%) from 88.338%
20675431244

push

github

tito10047
add TTL support for image caching: implement configurable TTL handling in cache services, adapt Twig components, and update tests to validate TTL functionality

12 of 12 new or added lines in 3 files covered. (100.0%)

120 existing lines in 9 files now uncovered.

615 of 694 relevant lines covered (88.62%)

184.23 hits per line

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

96.23
/src/Service/ResponsiveAttributeGenerator.php
1
<?php
2

3
/*
4
 * This file is part of the Progressive Image Bundle.
5
 *
6
 * (c) Jozef Môstka <https://github.com/tito10047/progressive-image-bundle>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
namespace Tito10047\ProgressiveImageBundle\Service;
13

14
use Tito10047\ProgressiveImageBundle\DTO\BreakpointAssignment;
15
use Tito10047\ProgressiveImageBundle\UrlGenerator\ResponsiveImageUrlGeneratorInterface;
16

17
final class ResponsiveAttributeGenerator
18
{
19
    /**
20
     * @param array{
21
     *      layouts: array<string, array{
22
     *      min_viewport: int,
23
     *      max_container: int|null
24
     *      }>,
25
     *      columns: int
26
     *      } $gridConfig
27
     * @param array<string, string> $ratioConfig
28
     */
29
    public function __construct(
30
        private array $gridConfig,
31
        private array $ratioConfig,
32
        private readonly PreloadCollector $preloadCollector,
33
        private ResponsiveImageUrlGeneratorInterface $urlGenerator,
34
    ) {
35
    }
269✔
36

37
    /**
38
     * @param BreakpointAssignment[] $assignments
39
     *
40
     * @return array{sizes: string, srcset: string}
41
     */
42
    public function generate(string $path, array $assignments, int $originalWidth, bool $preload, ?string $pointInterest = null): array
43
    {
44
        $assignments = $this->sortAssignments($assignments);
129✔
45

46
        $sizesParts = [];
129✔
47
        $srcsetParts = [];
129✔
48
        $processedWidths = [];
129✔
49

50
        foreach ($assignments as $assignment) {
129✔
51
            $layout = $this->gridConfig['layouts'][$assignment->breakpoint] ?? null;
129✔
52
            if (!$layout) {
129✔
53
                continue;
×
54
            }
55

56
            [$pixelWidth, $sizeValue] = $this->calculateDimensions($assignment, $layout);
129✔
57

58
            $size = $this->formatSizePart($layout['min_viewport'], $sizeValue);
129✔
59
            $sizesParts[] = $size;
129✔
60

61
            $url = $this->generateUrl($path, $assignment, (int) round($pixelWidth), $originalWidth, $processedWidths, $pointInterest);
129✔
62

63
            if ($url) {
129✔
64
                $actualPixelWidth = (int) round($pixelWidth);
129✔
65
                if ($preload) {
129✔
UNCOV
66
                    $this->preloadCollector->add($url, 'image', 'high', "{$actualPixelWidth}w", $size);
14✔
67
                }
68

69
                $srcsetParts[] = $url." {$actualPixelWidth}w";
129✔
70
            }
71
        }
72

73
        return [
129✔
74
            'sizes' => implode(', ', $sizesParts),
129✔
75
            'srcset' => implode(', ', $srcsetParts),
129✔
76
        ];
129✔
77
    }
78

79
    private function formatSizePart(int $minViewport, string $sizeValue): string
80
    {
81
        return $minViewport > 0
129✔
82
            ? "(min-width: {$minViewport}px) {$sizeValue}"
129✔
83
            : $sizeValue;
129✔
84
    }
85

86
    /**
87
     * @param BreakpointAssignment[] $assignments
88
     *
89
     * @return BreakpointAssignment[]
90
     */
91
    private function sortAssignments(array $assignments): array
92
    {
93
        usort($assignments, fn ($a, $b) => ($this->gridConfig['layouts'][$b->breakpoint]['min_viewport'] ?? 0) <=>
129✔
94
            ($this->gridConfig['layouts'][$a->breakpoint]['min_viewport'] ?? 0)
85✔
95
        );
129✔
96

97
        return $assignments;
129✔
98
    }
99

100
    /**
101
     * @param array{min_viewport: int, max_container: int|null} $layout
102
     *
103
     * @return array{0: float, 1: string}
104
     */
105
    private function calculateDimensions(BreakpointAssignment $assignment, array $layout): array
106
    {
107
        $totalCols = $this->gridConfig['columns'];
129✔
108
        $maxContainer = $layout['max_container'];
129✔
109

110
        if ($maxContainer) {
129✔
111
            // Fixný kontajner (napr. 1320px) -> šírka v px
112
            $pixelWidth = ($assignment->columns / $totalCols) * $maxContainer;
129✔
113
            $sizeValue = round($pixelWidth).'px';
129✔
114
        } else {
115
            // Fluid (null) -> šírka vo vw
116
            $vwWidth = ($assignment->columns / $totalCols) * 100;
85✔
117
            $sizeValue = round($vwWidth).'vw';
85✔
118
            // Pre výpočet URL odhadneme px šírku z nejakej rozumnej max-šírky (napr. 1920)
119
            $pixelWidth = ($vwWidth / 100) * 1920;
85✔
120
        }
121

122
        return [$pixelWidth, $sizeValue];
129✔
123
    }
124

125
    /**
126
     * @param array<int, bool> $processedWidths
127
     */
128
    private function generateUrl(
129
        string $path,
130
        BreakpointAssignment $assignment,
131
        int $basePixelWidth,
132
        int $originalWidth,
133
        array &$processedWidths,
134
        ?string $pointInterest = null,
135
    ): ?string {
136
        $ratio = $this->resolveRatio($assignment);
129✔
137

138
        if ($basePixelWidth > $originalWidth || isset($processedWidths[$basePixelWidth])) {
129✔
UNCOV
139
            return null;
56✔
140
        }
141

142
        $targetH = $ratio ? (int) round($basePixelWidth / $ratio) : null;
129✔
143
        $url = $this->urlGenerator->generateUrl($path, $basePixelWidth, $targetH, $pointInterest);
129✔
144

145
        $processedWidths[$basePixelWidth] = true;
129✔
146

147
        return $url;
129✔
148
    }
149

150
    private function resolveRatio(BreakpointAssignment $assignment): ?float
151
    {
152
        $ratioString = $assignment->ratio ?? null;
129✔
153
        if (!$ratioString) {
129✔
UNCOV
154
            return null;
84✔
155
        }
156

157
        // Ak je to kľúč v ratioConfig, použijeme ten
158
        if (isset($this->ratioConfig[$ratioString])) {
45✔
159
            return (float) $this->ratioConfig[$ratioString];
30✔
160
        }
161

162
        // Inak skúsime parsovať formát "3/4" alebo "3-4"
163
        if (preg_match('/^(\d+)[\/-](\d+)$/', $ratioString, $matches)) {
15✔
164
            return (float) $matches[1] / (float) $matches[2];
15✔
165
        }
166

167
        return null;
×
168
    }
169
}
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