Completed
Branch master (897f70)
by Sam
03:16 queued 01:43
created

AlignedBuilder::compareResourceRecords()   B

Complexity

Conditions 7
Paths 16

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 12
cts 12
cp 1
rs 8.6346
c 0
b 0
f 0
cc 7
nc 16
nop 2
crap 7
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()) {
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 = (null === $a->getRdata()) ? '' : $a->getRdata()->output();
127 4
        $b_rdata = (null === $b->getRdata()) ? '' : $b->getRdata()->output();
128
129 4
        if ($a->getType() === $b->getType()) {
130 4
            return strcmp($a->getName().$a_rdata, $b->getName().$b_rdata);
131
        }
132
133 4
        $_a = array_search($a->getType(), self::$order);
134 4
        $_b = array_search($b->getType(), self::$order);
135
136 4
        if (is_int($_a) && is_int($_b)) {
137 4
            return $_a - $_b;
138
        }
139
140 3
        if (false === $_a) {
141 1
            return 1;
142
        }
143
144 3
        return -1;
145
    }
146
147
    /**
148
     * @param RdataInterface $rdata
149
     * @param int            $padding
150
     *
151
     * @return string
152
     */
153 3
    private static function generateRdataOutput(RdataInterface $rdata, int $padding): string
154
    {
155 3
        if ($rdata instanceof SOA) {
156 3
            return self::outputSoa($rdata, $padding);
157
        }
158
159 3
        if ($rdata instanceof APL) {
160 1
            return self::outputApl($rdata, $padding);
161
        }
162
163 3
        if ($rdata instanceof LOC) {
164 3
            return self::outputLoc($rdata, $padding);
165
        }
166
167 3
        return $rdata->output();
168
    }
169
170
    /**
171
     * @param SOA $rdata
172
     * @param int $padding
173
     *
174
     * @return string
175
     */
176 3
    private static function outputSoa(SOA $rdata, int $padding): string
177
    {
178
        $vars = [
179 3
            $rdata->getMname(),
180 3
            $rdata->getRname(),
181 3
            $rdata->getSerial(),
182 3
            $rdata->getRefresh(),
183 3
            $rdata->getRetry(),
184 3
            $rdata->getExpire(),
185 3
            $rdata->getMinimum(),
186
        ];
187
188 3
        $longestVarLength = max(array_map('strlen', $vars));
189
190 3
        return self::MULTILINE_BEGIN.PHP_EOL.
191 3
        self::makeLine((string) $rdata->getMname(), 'MNAME', $longestVarLength, $padding).
192 3
        self::makeLine((string) $rdata->getRname(), 'RNAME', $longestVarLength, $padding).
193 3
        self::makeLine((string) $rdata->getSerial(), 'SERIAL', $longestVarLength, $padding).
194 3
        self::makeLine((string) $rdata->getRefresh(), 'REFRESH', $longestVarLength, $padding).
195 3
        self::makeLine((string) $rdata->getRetry(), 'RETRY', $longestVarLength, $padding).
196 3
        self::makeLine((string) $rdata->getExpire(), 'EXPIRE', $longestVarLength, $padding).
197 3
        self::makeLine((string) $rdata->getMinimum(), 'MINIMUM', $longestVarLength, $padding).
198 3
        str_repeat(' ', $padding).self::MULTILINE_END;
199
    }
200
201
    /**
202
     * @param APL $rdata
203
     * @param int $padding
204
     *
205
     * @return string
206
     */
207 1
    private static function outputApl(APL $rdata, int $padding): string
208
    {
209 1
        $blocks = explode(' ', $rdata->output());
210 1
        $longestVarLength = max(array_map('strlen', $blocks));
211 1
        $string = self::MULTILINE_BEGIN.PHP_EOL;
212
213 1
        foreach ($blocks as $block) {
214 1
            $string .= self::makeLine($block, null, $longestVarLength, $padding);
215
        }
216
217 1
        return $string.str_repeat(' ', $padding).self::MULTILINE_END;
218
    }
219
220
    /**
221
     * @param LOC $rdata
222
     * @param int $padding
223
     *
224
     * @return string
225
     */
226 3
    private static function outputLoc(LOC $rdata, int $padding): string
227
    {
228
        $parts = [
229 3
            $rdata->getLatitude(LOC::FORMAT_DMS),
230 3
            $rdata->getLongitude(LOC::FORMAT_DMS),
231 3
            sprintf('%.2fm', $rdata->getAltitude()),
232 3
            sprintf('%.2fm', $rdata->getSize()),
233 3
            sprintf('%.2fm', $rdata->getHorizontalPrecision()),
234 3
            sprintf('%.2fm', $rdata->getVerticalPrecision()),
235
        ];
236
237 3
        $longestVarLength = max(array_map('strlen', $parts));
238
239 3
        return self::MULTILINE_BEGIN.PHP_EOL.
240 3
            self::makeLine((string) $rdata->getLatitude(LOC::FORMAT_DMS), 'LATITUDE', $longestVarLength, $padding).
241 3
            self::makeLine((string) $rdata->getLongitude(LOC::FORMAT_DMS), 'LONGITUDE', $longestVarLength, $padding).
242 3
            self::makeLine(sprintf('%.2fm', $rdata->getAltitude()), 'ALTITUDE', $longestVarLength, $padding).
243 3
            self::makeLine(sprintf('%.2fm', $rdata->getSize()), 'SIZE', $longestVarLength, $padding).
244 3
            self::makeLine(sprintf('%.2fm', $rdata->getHorizontalPrecision()), 'HORIZONTAL PRECISION', $longestVarLength, $padding).
245 3
            self::makeLine(sprintf('%.2fm', $rdata->getVerticalPrecision()), 'VERTICAL PRECISION', $longestVarLength, $padding).
246 3
            str_repeat(' ', $padding).self::MULTILINE_END;
247
    }
248
249
    /**
250
     * Returns a padded line with comment.
251
     *
252
     * @param string $text
253
     * @param string $comment
254
     * @param int    $longestVarLength
255
     * @param int    $padding
256
     *
257
     * @return string
258
     */
259 3
    private static function makeLine(string $text, ?string $comment, int $longestVarLength, int $padding): string
260
    {
261 3
        $output = str_repeat(' ', $padding).str_pad($text, $longestVarLength);
262
263 3
        if (null !== $comment) {
264 3
            $output .= ' '.self::COMMENT_DELIMINATOR.$comment;
265
        }
266
267 3
        return $output.PHP_EOL;
268
    }
269
270
    /**
271
     * Get the padding required for a zone.
272
     *
273
     * @param Zone $zone
274
     *
275
     * @return int[] Array order: name, ttl, type, rdata
276
     */
277 3
    private static function getPadding(Zone $zone): array
278
    {
279 3
        $name = $ttl = $type = 0;
280
281 3
        foreach ($zone as $resourceRecord) {
282 3
            $name = max($name, strlen($resourceRecord->getName()));
283 3
            $ttl = max($ttl, strlen($resourceRecord->getTtl()));
284 3
            $type = max($type, strlen($resourceRecord->getType()));
285
        }
286
287
        return [
288 3
            $name,
289 3
            $ttl,
290 3
            $type,
291 3
            $name + $ttl + $type + 6,
292
        ];
293
    }
294
}
295