Completed
Push — master ( dfe0cd...94ecc1 )
by Sam
05:40 queued 02:43
created

AlignedBuilder::getOrder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
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\NSEC3;
26
use Badcow\DNS\Rdata\NSEC3PARAM;
27
use Badcow\DNS\Rdata\PTR;
28
use Badcow\DNS\Rdata\RdataInterface;
29
use Badcow\DNS\Rdata\RRSIG;
30
use Badcow\DNS\Rdata\SOA;
31
use Badcow\DNS\Rdata\SRV;
32
use Badcow\DNS\Rdata\TXT;
33
34
class AlignedBuilder
35
{
36
    /**
37
     * The order in which Resource Records should appear in a zone.
38
     *
39
     * @var array
40
     */
41
    private $order = [
42
        SOA::TYPE,
43
        NS::TYPE,
44
        A::TYPE,
45
        AAAA::TYPE,
46
        CNAME::TYPE,
47
        DNAME::TYPE,
48
        MX::TYPE,
49
        LOC::TYPE,
50
        HINFO::TYPE,
51
        TXT::TYPE,
52
        PTR::TYPE,
53
        SRV::TYPE,
54
        NSEC3::TYPE,
55
        NSEC3PARAM::TYPE,
56
        RRSIG::TYPE,
57
    ];
58
59
    /**
60
     * @var callable[] array of Rdata type indexed, callables that handle the output formatting of Rdata
61
     */
62
    private $rdataFormatters = [];
63
64 4
    public function __construct()
65
    {
66 4
        $this->rdataFormatters = AlignedRdataFormatters::$rdataFormatters;
67 4
    }
68
69
    /**
70
     * Adds or changes an Rdata output formatter.
71
     *
72
     * @param string   $type      the Rdata type to be handled by the $formatter
73
     * @param callable $formatter callable that will handle the output formatting of the Rdata
74
     */
75
    public function addRdataFormatter(string $type, callable $formatter): void
76
    {
77
        $this->rdataFormatters[$type] = $formatter;
78
    }
79
80
    public function getRdataFormatters(): array
81
    {
82
        return $this->rdataFormatters;
83
    }
84
85
    /**
86
     * @return string[]
87
     */
88
    public function getOrder(): array
89
    {
90
        return $this->order;
91
    }
92
93
    /**
94
     * Set the order in which Resource Records should appear in a zone..
95
     *
96
     * @param string[] $order Simple string array of Rdata types
97
     */
98
    public function setOrder(array $order): void
99
    {
100
        $this->order = $order;
101
    }
102
103
    /**
104
     * Build an aligned BIND zone file.
105
     */
106 3
    public function build(Zone $zone): string
107
    {
108 3
        $master = self::generateControlEntries($zone);
109 3
        $resourceRecords = $zone->getResourceRecords();
110 3
        $current = SOA::TYPE;
111 3
        usort($resourceRecords, [__CLASS__, 'compareResourceRecords']);
112
113 3
        list($namePadding, $ttlPadding, $typePadding, $classPadding, $rdataPadding) = self::getPadding($zone);
114
115 3
        foreach ($resourceRecords as $resourceRecord) {
116 3
            $rdata = $resourceRecord->getRdata();
117 3
            if (null == $rdata) {
118 1
                continue;
119
            }
120
121 3
            if ($rdata->getType() !== $current) {
122 3
                $master .= Tokens::LINE_FEED.Tokens::SEMICOLON.Tokens::SPACE.$rdata->getType().' RECORDS'.Tokens::LINE_FEED;
123 3
                $current = $rdata->getType();
124
            }
125
126 3
            $master .= sprintf('%s %s %s %s %s',
127 3
                str_pad((string) $resourceRecord->getName(), $namePadding, Tokens::SPACE, STR_PAD_RIGHT),
128 3
                str_pad((string) $resourceRecord->getTtl(), $ttlPadding, Tokens::SPACE, STR_PAD_RIGHT),
129 3
                str_pad((string) $resourceRecord->getClass(), $classPadding, Tokens::SPACE, STR_PAD_RIGHT),
130 3
                str_pad($rdata->getType(), $typePadding, Tokens::SPACE, STR_PAD_RIGHT),
131 3
                $this->generateRdataOutput($rdata, $rdataPadding)
132
            );
133
134 3
            $master .= self::generateComment($resourceRecord);
135 3
            $master .= Tokens::LINE_FEED;
136
        }
137
138 3
        return $master;
139
    }
140
141
    /**
142
     * Returns the control entries as strings.
143
     */
144 3
    private static function generateControlEntries(Zone $zone): string
145
    {
146 3
        $master = '$ORIGIN '.$zone->getName().Tokens::LINE_FEED;
147 3
        if (null !== $zone->getDefaultTtl()) {
0 ignored issues
show
introduced by
The condition null !== $zone->getDefaultTtl() is always true.
Loading history...
148 3
            $master .= '$TTL '.$zone->getDefaultTtl().Tokens::LINE_FEED;
149
        }
150
151 3
        return $master;
152
    }
153
154
    /**
155
     * Returns a comment string if the comments are not null, returns empty string otherwise.
156
     */
157 3
    private static function generateComment(ResourceRecord $resourceRecord): string
158
    {
159 3
        if (null !== $resourceRecord->getComment()) {
160 3
            return Tokens::SEMICOLON.Tokens::SPACE.$resourceRecord->getComment();
161
        }
162
163 3
        return '';
164
    }
165
166
    /**
167
     * Compares two ResourceRecords to determine which is the higher order. Used with the usort() function.
168
     *
169
     * @param ResourceRecord $a The first ResourceRecord
170
     * @param ResourceRecord $b The second ResourceRecord
171
     *
172
     * @return int $a is higher precedence than $b if return value is less than 0.
173
     *             $b is higher precedence than $a if return value is greater than 0.
174
     *             $a and $b have the same precedence if the return value is 0.
175
     */
176 4
    public function compareResourceRecords(ResourceRecord $a, ResourceRecord $b): int
177
    {
178 4
        $a_rdata = (null === $a->getRdata()) ? '' : $a->getRdata()->toText();
179 4
        $b_rdata = (null === $b->getRdata()) ? '' : $b->getRdata()->toText();
180
181
        //If the types are the same, do a simple alphabetical comparison.
182 4
        if ($a->getType() === $b->getType()) {
183 4
            return strcmp($a->getName().$a_rdata, $b->getName().$b_rdata);
184
        }
185
186
        //Find the precedence (if any) for the two types.
187 4
        $_a = array_search($a->getType(), $this->order);
188 4
        $_b = array_search($b->getType(), $this->order);
189
190
        //If neither types have defined precedence.
191 4
        if (!is_int($_a) && !is_int($_b)) {
192 1
            return strcmp($a->getType() ?? '', $b->getType() ?? '');
193
        }
194
195
        //If both types have defined precedence.
196 4
        if (is_int($_a) && is_int($_b)) {
197 4
            return $_a - $_b;
198
        }
199
200
        //If only $b has defined precedence.
201 3
        if (false === $_a) {
202 1
            return 1;
203
        }
204
205
        //If only $a has defined precedence.
206 3
        return -1;
207
    }
208
209
    /**
210
     * Composes the RDATA of the Resource Record.
211
     *
212
     * @param RdataInterface $rdata   the Rdata to be formatted
213
     * @param int            $padding the number of spaces before the Rdata column
214
     */
215 3
    private function generateRdataOutput(RdataInterface $rdata, int $padding): string
216
    {
217 3
        if (array_key_exists($rdata->getType(), $this->rdataFormatters)) {
218 3
            return call_user_func($this->rdataFormatters[$rdata->getType()], $rdata, $padding);
219
        }
220
221 3
        return $rdata->toText();
222
    }
223
224
    /**
225
     * Get the padding required for a zone.
226
     *
227
     * @param Zone $zone the DNS Zone being processed
228
     *
229
     * @return int[] Array order: [name, ttl, type, class, rdata]
230
     */
231 3
    private static function getPadding(Zone $zone): array
232
    {
233 3
        $name = $ttl = $type = 0;
234 3
        $class = 2;
235
236
        /** @var ResourceRecord $resourceRecord */
237 3
        foreach ($zone as $resourceRecord) {
238 3
            $name = max($name, strlen($resourceRecord->getName() ?? ''));
239 3
            $ttl = max($ttl, strlen((string) $resourceRecord->getTtl()));
240 3
            $class = max($class, strlen($resourceRecord->getClass() ?? ''));
241 3
            $type = max($type, strlen($resourceRecord->getType() ?? ''));
242
        }
243
244
        return [
245 3
            $name,
246 3
            $ttl,
247 3
            $type,
248 3
            $class,
249 3
            $name + $ttl + $class + $type + 4,
250
        ];
251
    }
252
}
253