Passed
Branch master (8940db)
by Sam
02:38
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
declare(strict_types=1);
4
5
/*
6
 * This file is part of Badcow DNS Library.
7
 *
8
 * (c) Samuel Williams <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Badcow\DNS;
15
16
use Badcow\DNS\Parser\Tokens;
17
use Badcow\DNS\Rdata\A;
18
use Badcow\DNS\Rdata\AAAA;
19
use Badcow\DNS\Rdata\CNAME;
20
use Badcow\DNS\Rdata\DNAME;
21
use Badcow\DNS\Rdata\HINFO;
22
use Badcow\DNS\Rdata\LOC;
23
use Badcow\DNS\Rdata\MX;
24
use Badcow\DNS\Rdata\NS;
25
use Badcow\DNS\Rdata\PTR;
26
use Badcow\DNS\Rdata\RdataInterface;
27
use Badcow\DNS\Rdata\SOA;
28
use Badcow\DNS\Rdata\SRV;
29
use Badcow\DNS\Rdata\TXT;
30
31
class AlignedBuilder
32
{
33
    /**
34
     * The order in which Resource Records should appear in a zone.
35
     *
36
     * @var array
37
     */
38
    private static $order = [
39
        SOA::TYPE,
40
        NS::TYPE,
41
        A::TYPE,
42
        AAAA::TYPE,
43
        CNAME::TYPE,
44
        DNAME::TYPE,
45
        MX::TYPE,
46
        LOC::TYPE,
47
        HINFO::TYPE,
48
        TXT::TYPE,
49
        PTR::TYPE,
50
        SRV::TYPE,
51
    ];
52
53
    /**
54
     * @param Zone $zone
55
     *
56
     * @return string
57
     */
58 3
    public static function build(Zone $zone): string
59
    {
60 3
        $master = self::generateControlEntries($zone);
61 3
        $resourceRecords = $zone->getResourceRecords();
62 3
        $current = SOA::TYPE;
63 3
        usort($resourceRecords, [__CLASS__, 'compareResourceRecords']);
64
65 3
        list($namePadding, $ttlPadding, $typePadding, $rdataPadding) = self::getPadding($zone);
66
67 3
        foreach ($resourceRecords as $resourceRecord) {
68 3
            $rdata = $resourceRecord->getRdata();
69 3
            if (null == $rdata) {
70 1
                continue;
71
            }
72
73 3
            if ($resourceRecord->getType() !== $current) {
74 3
                $master .= Tokens::LINE_FEED.Tokens::SEMICOLON.Tokens::SPACE.$resourceRecord->getType().' RECORDS'.Tokens::LINE_FEED;
75 3
                $current = $resourceRecord->getType();
76
            }
77
78 3
            $master .= sprintf('%s %s %s %s %s',
79 3
                str_pad((string) $resourceRecord->getName(), $namePadding, ' ', STR_PAD_RIGHT),
80 3
                str_pad((string) $resourceRecord->getTtl(), $ttlPadding, ' ', STR_PAD_RIGHT),
81 3
                str_pad((string) $resourceRecord->getClass(), 2, ' ', STR_PAD_RIGHT),
82 3
                str_pad($rdata->getType(), $typePadding, ' ', STR_PAD_RIGHT),
83 3
                self::generateRdataOutput($rdata, $rdataPadding)
84
            );
85
86 3
            $master .= self::generateComment($resourceRecord);
87 3
            $master .= Tokens::LINE_FEED;
88
        }
89
90 3
        return $master;
91
    }
92
93 3
    private static function generateControlEntries(Zone $zone): string
94
    {
95 3
        $master = '$ORIGIN '.$zone->getName().Tokens::LINE_FEED;
96 3
        if (null !== $zone->getDefaultTtl()) {
0 ignored issues
show
introduced by
The condition null !== $zone->getDefaultTtl() is always true.
Loading history...
97 3
            $master .= '$TTL '.$zone->getDefaultTtl().Tokens::LINE_FEED;
98
        }
99
100 3
        return $master;
101
    }
102
103 3
    private static function generateComment(ResourceRecord $resourceRecord): string
104
    {
105 3
        if (null !== $resourceRecord->getComment()) {
106 3
            return Tokens::SEMICOLON.Tokens::SPACE.$resourceRecord->getComment();
107
        }
108
109 3
        return '';
110
    }
111
112
    /**
113
     * Compares two ResourceRecords to determine which is the higher order. Used with the usort() function.
114
     *
115
     * @param ResourceRecord $a
116
     * @param ResourceRecord $b
117
     *
118
     * @return int
119
     */
120 4
    public static function compareResourceRecords(ResourceRecord $a, ResourceRecord $b): int
121
    {
122 4
        $a_rdata = (null === $a->getRdata()) ? '' : $a->getRdata()->toText();
123 4
        $b_rdata = (null === $b->getRdata()) ? '' : $b->getRdata()->toText();
124
125 4
        if ($a->getType() === $b->getType()) {
126 4
            return strcmp($a->getName().$a_rdata, $b->getName().$b_rdata);
127
        }
128
129 4
        $_a = array_search($a->getType(), self::$order);
130 4
        $_b = array_search($b->getType(), self::$order);
131
132 4
        if (is_int($_a) && is_int($_b)) {
133 4
            return $_a - $_b;
134
        }
135
136 3
        if (false === $_a) {
137 1
            return 1;
138
        }
139
140 3
        return -1;
141
    }
142
143
    /**
144
     * @param RdataInterface $rdata
145
     * @param int            $padding
146
     *
147
     * @return string
148
     */
149 3
    private static function generateRdataOutput(RdataInterface $rdata, int $padding): string
150
    {
151 3
        $rdataFormatters = AlignedRdataFormatters::getRdataFormatters();
152 3
        if (array_key_exists($rdata->getType(), $rdataFormatters)) {
153 3
            return call_user_func($rdataFormatters[$rdata->getType()], $rdata, $padding);
154
        }
155
156 3
        return $rdata->toText();
157
    }
158
159
    /**
160
     * Get the padding required for a zone.
161
     *
162
     * @param Zone $zone
163
     *
164
     * @return int[] Array order: name, ttl, type, rdata
165
     */
166 3
    private static function getPadding(Zone $zone): array
167
    {
168 3
        $name = $ttl = $type = 0;
169
170 3
        foreach ($zone as $resourceRecord) {
171 3
            $name = max($name, strlen($resourceRecord->getName() ?? ''));
172 3
            $ttl = max($ttl, strlen((string) $resourceRecord->getTtl()));
173 3
            $type = max($type, strlen($resourceRecord->getType() ?? ''));
174
        }
175
176
        return [
177 3
            $name,
178 3
            $ttl,
179 3
            $type,
180 3
            $name + $ttl + $type + 6,
181
        ];
182
    }
183
}
184