AbstractAcceptHeader::add()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
cc 4
eloc 12
nc 3
nop 1
1
<?php
2
/*
3
 * This file is part of the Borobudur-Http package.
4
 *
5
 * (c) Hexacodelabs <http://hexacodelabs.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Borobudur\Http\Header\Accept;
12
13
use Borobudur\Http\Exception\InvalidArgumentException;
14
use Borobudur\Http\Header\HeaderInterface;
15
16
/**
17
 * @author      Iqbal Maulana <[email protected]>
18
 * @created     7/18/15
19
 */
20
abstract class AbstractAcceptHeader implements HeaderInterface
21
{
22
    /**
23
     * @var AcceptHeaderItem[]
24
     */
25
    private $items;
26
27
    /**
28
     * @var bool
29
     */
30
    private $sorted = true;
31
32
    /**
33
     * Constructor.
34
     *
35
     * @param AcceptHeaderItem[] $items
36
     */
37
    public function __construct(array $items = array())
38
    {
39
        foreach ($items as $item) {
40
            $this->add($item);
41
        }
42
    }
43
44
    /**
45
     * Add an item to header and reset sorting.
46
     *
47
     * @param AcceptHeaderItem $item
48
     *
49
     * @return $this
50
     *
51
     * @throws InvalidArgumentException
52
     */
53
    public function add(AcceptHeaderItem $item)
54
    {
55
        $matcher = $this->getFieldValueMatcher();
56
        if (null !== $matcher && false === (bool)preg_match($matcher, $item->getValue())) {
57
            throw new InvalidArgumentException(sprintf(
58
                'Item value "%s" is not match for given rules of "%s" header.',
59
                $item->getValue(),
60
                $this->getFieldName()
61
            ));
62
        }
63
64
        if (null === $item->getIndex()) {
65
            $item->setIndex(count($this->items));
66
        }
67
68
        $this->items[$item->getValue()] = $item;
69
        $this->sorted = false;
70
71
        return $this;
72
    }
73
74
    /**
75
     * Return accept header value regex.
76
     *
77
     * @return string
78
     */
79
    abstract protected function getFieldValueMatcher();
80
81
    /**
82
     * Factory create AcceptHeader from string representation.
83
     *
84
     * @param string $headerLine
85
     *
86
     * @return $this
87
     *
88
     * @throws InvalidArgumentException
89
     */
90
    public static function fromString($headerLine)
91
    {
92
        $headerLine = (string)$headerLine;
93
        $pos = strpos($headerLine, ':');
94
        $fieldName = substr($headerLine, 0, $pos);
95
        $header = new static();
96
        $items = array();
97
98
        if ($fieldName !== $header->getFieldName()) {
99
            throw new InvalidArgumentException(sprintf(
100
                'Header "%s" can\'t parse value "%s".',
101
                $header->getFieldName(),
102
                $fieldName
103
            ));
104
        }
105
106
        preg_match_all(
107
            '#((("[^"\']++")|(\'[^"\']++\'))|[^,"\'])+#',
108
            trim(substr($headerLine, $pos + 1)),
109
            $items
110
        );
111
112
        foreach (array_map('trim', $items[0]) as $index => $itemValueString) {
113
            $item = AcceptHeaderItem::fromString($itemValueString);
114
            $item->setIndex($index);
115
            $header->add($item);
116
        }
117
118
        return $header;
119
    }
120
121
    /**
122
     * Return sorted all items.
123
     *
124
     * @return AcceptHeaderItem[]
125
     */
126
    public function all()
127
    {
128
        $this->sort();
129
130
        return $this->items;
131
    }
132
133
    /**
134
     * Sort items by descending quality.
135
     */
136
    private function sort()
137
    {
138
        if (false === $this->sorted) {
139
            uasort($this->items, function (AcceptHeaderItem $x, AcceptHeaderItem $y) {
140
                $xPriority = $x->getPriority();
141
                $yPriority = $y->getPriority();
142
143
                if ($xPriority === $yPriority) {
144
                    // if quality same, then sort by index
145
                    // lower index must be first
146
                    return $x->getIndex() < $y->getIndex() ? -1 : 1;
147
                }
148
149
                return $xPriority > $yPriority ? -1 : 1;
150
            });
151
152
            // mark as sorted
153
            $this->sorted = true;
154
        }
155
    }
156
157
    /**
158
     * Get item with header value.
159
     *
160
     * @param string $value
161
     *
162
     * @return AcceptHeaderItem|null
163
     */
164
    public function get($value)
165
    {
166
        if ($this->has($value)) {
167
            return $this->items[$value];
168
        }
169
170
        return null;
171
    }
172
173
    /**
174
     * Check if accept header has value.
175
     *
176
     * @param string $value
177
     *
178
     * @return bool
179
     */
180
    public function has($value)
181
    {
182
        return isset($this->items[$value]);
183
    }
184
185
    /**
186
     * Get first item.
187
     *
188
     * @return AcceptHeaderItem|null
189
     */
190
    public function first()
191
    {
192
        if (!empty($this->items)) {
193
            $this->sort();
194
195
            return reset($this->items);
196
        }
197
198
        return null;
199
    }
200
201
    /**
202
     * Cast accept header to string representation.
203
     *
204
     * @return string
205
     */
206
    public function __toString()
207
    {
208
        return sprintf('%s: %s', $this->getFieldName(), $this->getFieldValue());
209
    }
210
211
    /**
212
     * Get AcceptHeader as string representation.
213
     *
214
     * @return string
215
     */
216
    public function getFieldValue()
217
    {
218
        $this->sort();
219
220
        return implode(',', $this->items);
221
    }
222
}
223