Completed
Branch phpstan (92b7c6)
by Sam
03:09
created

AlignedBuilder::outputLoc()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 16
cts 16
cp 1
rs 9.568
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
/*
4
 * This file is part of Badcow DNS Library.
5
 *
6
 * (c) Samuel Williams <[email protected]>
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 Badcow\DNS;
13
14
use Badcow\DNS\Rdata\A;
15
use Badcow\DNS\Rdata\AAAA;
16
use Badcow\DNS\Rdata\APL;
17
use Badcow\DNS\Rdata\CNAME;
18
use Badcow\DNS\Rdata\DNAME;
19
use Badcow\DNS\Rdata\HINFO;
20
use Badcow\DNS\Rdata\LOC;
21
use Badcow\DNS\Rdata\MX;
22
use Badcow\DNS\Rdata\NS;
23
use Badcow\DNS\Rdata\PTR;
24
use Badcow\DNS\Rdata\SOA;
25
use Badcow\DNS\Rdata\SRV;
26
use Badcow\DNS\Rdata\TXT;
27
use Badcow\DNS\Rdata\RdataInterface;
28
29
class AlignedBuilder
30
{
31
    const COMMENT_DELIMINATOR = '; ';
32
33
    const MULTILINE_BEGIN = '(';
34
35
    const MULTILINE_END = ')';
36
37
    /**
38
     * The order in which Resource Records should appear in a zone.
39
     *
40
     * @var array
41
     */
42
    private static $order = [
43
        SOA::TYPE,
44
        NS::TYPE,
45
        A::TYPE,
46
        AAAA::TYPE,
47
        CNAME::TYPE,
48
        DNAME::TYPE,
49
        MX::TYPE,
50
        LOC::TYPE,
51
        HINFO::TYPE,
52
        TXT::TYPE,
53
        PTR::TYPE,
54
        SRV::TYPE,
55
    ];
56
57
    /**
58
     * @param Zone $zone
59
     *
60
     * @return string
61
     */
62 3
    public static function build(Zone $zone): string
63
    {
64 3
        $master = self::generateControlEntries($zone);
65 3
        $resourceRecords = $zone->getResourceRecords();
66 3
        $current = SOA::TYPE;
67 3
        usort($resourceRecords, [__CLASS__, 'compareResourceRecords']);
68
69 3
        list($namePadding, $ttlPadding, $typePadding, $rdataPadding) = self::getPadding($zone);
70
71 3
        foreach ($resourceRecords as $resourceRecord) {
72 3
            $rdata = $resourceRecord->getRdata();
73 3
            if (null == $rdata) {
74 1
                continue;
75
            }
76
77 3
            if ($resourceRecord->getType() !== $current) {
78 3
                $master .= PHP_EOL.self::COMMENT_DELIMINATOR.$resourceRecord->getType().' RECORDS'.PHP_EOL;
79 3
                $current = $resourceRecord->getType();
80
            }
81
82 3
            $master .= sprintf('%s %s %s %s %s',
83 3
                str_pad((string) $resourceRecord->getName(), $namePadding, ' ', STR_PAD_RIGHT),
84 3
                str_pad((string) $resourceRecord->getTtl(), $ttlPadding, ' ', STR_PAD_RIGHT),
85 3
                str_pad((string) $resourceRecord->getClass(), 2, ' ', STR_PAD_RIGHT),
86 3
                str_pad($rdata->getType(), $typePadding, ' ', STR_PAD_RIGHT),
87 3
                self::generateRdataOutput($rdata, $rdataPadding)
88
            );
89
90 3
            $master .= self::generateComment($resourceRecord);
91 3
            $master .= PHP_EOL;
92
        }
93
94 3
        return $master;
95
    }
96
97 3
    private static function generateControlEntries(Zone $zone): string
98
    {
99 3
        $master = '$ORIGIN '.$zone->getName().PHP_EOL;
100 3
        if (null !== $zone->getDefaultTtl()) {
101 3
            $master .= '$TTL '.$zone->getDefaultTtl().PHP_EOL;
102
        }
103
104 3
        return $master;
105
    }
106
107 3
    private static function generateComment(ResourceRecord $resourceRecord): string
108
    {
109 3
        if (null != $resourceRecord->getComment()) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $resourceRecord->getComment() of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
110 3
            return self::COMMENT_DELIMINATOR.$resourceRecord->getComment();
111
        }
112
113 3
        return '';
114
    }
115
116
    /**
117
     * Compares two ResourceRecords to determine which is the higher order. Used with the usort() function.
118
     *
119
     * @param ResourceRecord $a
120
     * @param ResourceRecord $b
121
     *
122
     * @return int
123
     */
124 4
    public static function compareResourceRecords(ResourceRecord $a, ResourceRecord $b): int
125
    {
126 4
        $a_rdata = $b_rdata = '';
127
128 4
        if (null !== $a->getRdata()) {
129 4
            $a_rdata = $a->getRdata()->output();
130
        }
131
132 4
        if (null !== $b->getRdata()) {
133 4
            $b_rdata = $b->getRdata()->output();
134
        }
135
136 4
        if ($a->getType() === $b->getType()) {
137 4
            return strcmp($a->getName().$a_rdata, $b->getName().$b_rdata);
138
        }
139
140 4
        $_a = array_search($a->getType(), self::$order);
141 4
        $_b = array_search($b->getType(), self::$order);
142
143 4
        if (is_int($_a) && is_int($_b)) {
144 4
            return $_a - $_b;
145
        }
146
147 3
        if (false === $_a) {
148 1
            return 1;
149
        }
150
151 3
        return -1;
152
    }
153
154
    /**
155
     * @param RdataInterface $rdata
156
     * @param int            $padding
157
     *
158
     * @return string
159
     */
160 3
    private static function generateRdataOutput(RdataInterface $rdata, int $padding): string
161
    {
162 3
        if ($rdata instanceof SOA) {
163 3
            return self::outputSoa($rdata, $padding);
164
        }
165
166 3
        if ($rdata instanceof APL) {
167 1
            return self::outputApl($rdata, $padding);
168
        }
169
170 3
        if ($rdata instanceof LOC) {
171 3
            return self::outputLoc($rdata, $padding);
172
        }
173
174 3
        return $rdata->output();
175
    }
176
177
    /**
178
     * @param SOA $rdata
179
     * @param int $padding
180
     *
181
     * @return string
182
     */
183 3
    private static function outputSoa(SOA $rdata, int $padding): string
184
    {
185
        $vars = [
186 3
            $rdata->getMname(),
187 3
            $rdata->getRname(),
188 3
            $rdata->getSerial(),
189 3
            $rdata->getRefresh(),
190 3
            $rdata->getRetry(),
191 3
            $rdata->getExpire(),
192 3
            $rdata->getMinimum(),
193
        ];
194
195 3
        $longestVarLength = max(array_map('strlen', $vars));
196
197 3
        return self::MULTILINE_BEGIN.PHP_EOL.
198 3
        self::makeLine((string) $rdata->getMname(), 'MNAME', $longestVarLength, $padding).
199 3
        self::makeLine((string) $rdata->getRname(), 'RNAME', $longestVarLength, $padding).
200 3
        self::makeLine((string) $rdata->getSerial(), 'SERIAL', $longestVarLength, $padding).
201 3
        self::makeLine((string) $rdata->getRefresh(), 'REFRESH', $longestVarLength, $padding).
202 3
        self::makeLine((string) $rdata->getRetry(), 'RETRY', $longestVarLength, $padding).
203 3
        self::makeLine((string) $rdata->getExpire(), 'EXPIRE', $longestVarLength, $padding).
204 3
        self::makeLine((string) $rdata->getMinimum(), 'MINIMUM', $longestVarLength, $padding).
205 3
        str_repeat(' ', $padding).self::MULTILINE_END;
206
    }
207
208
    /**
209
     * @param APL $rdata
210
     * @param int $padding
211
     *
212
     * @return string
213
     */
214 1
    private static function outputApl(APL $rdata, int $padding): string
215
    {
216 1
        $blocks = explode(' ', $rdata->output());
217 1
        $longestVarLength = max(array_map('strlen', $blocks));
218 1
        $string = self::MULTILINE_BEGIN.PHP_EOL;
219
220 1
        foreach ($blocks as $block) {
221 1
            $string .= self::makeLine($block, null, $longestVarLength, $padding);
222
        }
223
224 1
        return $string.str_repeat(' ', $padding).self::MULTILINE_END;
225
    }
226
227
    /**
228
     * @param LOC $rdata
229
     * @param int $padding
230
     *
231
     * @return string
232
     */
233 3
    private static function outputLoc(LOC $rdata, int $padding): string
234
    {
235
        $parts = [
236 3
            $rdata->getLatitude(LOC::FORMAT_DMS),
237 3
            $rdata->getLongitude(LOC::FORMAT_DMS),
238 3
            sprintf('%.2fm', $rdata->getAltitude()),
239 3
            sprintf('%.2fm', $rdata->getSize()),
240 3
            sprintf('%.2fm', $rdata->getHorizontalPrecision()),
241 3
            sprintf('%.2fm', $rdata->getVerticalPrecision()),
242
        ];
243
244 3
        $longestVarLength = max(array_map('strlen', $parts));
245
246 3
        return self::MULTILINE_BEGIN.PHP_EOL.
247 3
            self::makeLine((string) $rdata->getLatitude(LOC::FORMAT_DMS), 'LATITUDE', $longestVarLength, $padding).
248 3
            self::makeLine((string) $rdata->getLongitude(LOC::FORMAT_DMS), 'LONGITUDE', $longestVarLength, $padding).
249 3
            self::makeLine(sprintf('%.2fm', $rdata->getAltitude()), 'ALTITUDE', $longestVarLength, $padding).
250 3
            self::makeLine(sprintf('%.2fm', $rdata->getSize()), 'SIZE', $longestVarLength, $padding).
251 3
            self::makeLine(sprintf('%.2fm', $rdata->getHorizontalPrecision()), 'HORIZONTAL PRECISION', $longestVarLength, $padding).
252 3
            self::makeLine(sprintf('%.2fm', $rdata->getVerticalPrecision()), 'VERTICAL PRECISION', $longestVarLength, $padding).
253 3
            str_repeat(' ', $padding).self::MULTILINE_END;
254
    }
255
256
    /**
257
     * Returns a padded line with comment.
258
     *
259
     * @param string $text
260
     * @param string $comment
261
     * @param int    $longestVarLength
262
     * @param int    $padding
263
     *
264
     * @return string
265
     */
266 3
    private static function makeLine(string $text, ?string $comment, int $longestVarLength, int $padding): string
267
    {
268 3
        $output = str_repeat(' ', $padding).str_pad($text, $longestVarLength);
269
270 3
        if (null !== $comment) {
271 3
            $output .= ' '.self::COMMENT_DELIMINATOR.$comment;
272
        }
273
274 3
        return $output.PHP_EOL;
275
    }
276
277
    /**
278
     * Get the padding required for a zone.
279
     *
280
     * @param Zone $zone
281
     *
282
     * @return array Array order: name, ttl, type, rdata
283
     */
284 3
    private static function getPadding(Zone $zone)
285
    {
286 3
        $name = $ttl = $type = 0;
287
288 3
        foreach ($zone as $resourceRecord) {
289 3
            $name = max($name, strlen($resourceRecord->getName()));
290 3
            $ttl = max($ttl, strlen($resourceRecord->getTtl()));
291 3
            $type = max($type, strlen($resourceRecord->getType()));
292
        }
293
294
        return [
295 3
            $name,
296 3
            $ttl,
297 3
            $type,
298 3
            $name + $ttl + $type + 6,
299
        ];
300
    }
301
}
302