Completed
Push — master ( 612ab5...954270 )
by Auke
02:57 queued 01:02
created

headers::getCacheControlTime()   C

Complexity

Conditions 13
Paths 26

Size

Total Lines 38
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 13.0343

Importance

Changes 3
Bugs 1 Features 3
Metric Value
c 3
b 1
f 3
dl 0
loc 38
ccs 32
cts 34
cp 0.9412
rs 5.1234
cc 13
eloc 31
nc 26
nop 2
crap 13.0343

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Ariadne Component Library.
5
 *
6
 * (c) Muze <[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 arc\http;
13
14
/**
15
 * Class headers
16
 * @package arc\http
17
 */
18
final class headers
19
{
20
21
    /**
22
     * Parse response headers string from a HTTP request into an array of headers. e.g.
23
     * [ 'Location' => 'http://www.example.com', ... ]
24
     * When multiple headers with the same name are present, all values will form an array, in the order in which
25
     * they are present in the source.
26
     * @param string|string[] $headers The headers string to parse.
27
     * @return array
28
     */
29 6
    public static function parse( $headers ) {
30 6
        if ( !is_array($headers) && !$headers instanceof \ArrayObject ) {
31 4
            $headers = array_filter(
32 4
                array_map( 'trim', explode( "\n", (string) $headers ) )
33 4
            );
34 4
        }
35 6
        $result = [];
36 6
        foreach( $headers as $key => $header ) {
37 6
            if ( !is_array($header) ) {
38 6
                $temp = array_map('trim', explode(':', $header, 2) );
39 6
            } else {
40
                $temp = $header;
41
            }
42 6
            if ( isset( $temp[1] ) ) {
43 6
                if ( !isset($result[ $temp[0]]) ) {
44
                    // first entry for this header
45 6
                    $result[ $temp[0] ] = $temp[1];
46 6
                } else if ( is_string($result[ $temp[0] ]) ) {
47
                    // second header entry with same name
48 2
                    $result[ $temp[0] ] = [
49 2
                        $result[ $temp[0] ],
50 2
                        $temp[1]
51 2
                    ];
52 2
                } else { // third or later header entry with same name
53 1
                    $result[ $temp[0] ][] = $temp[1];
54
                }
55 6
            } else if (is_numeric($key)) {
56 5
                $result[] = $temp[0];
57 5
            } else { // e.g. HTTP1/1 200 OK
58
                $result[$key] = $temp[0];
59
            }
60 6
        }
61 6
        return $result;
62
    }
63
64
    /**
65
     * Return the last value sent for a specific header, uses the output of parse().
66
     * @param (mixed) $headers An array with multiple header strings or a single string.
67
     * @return array|mixed
68
     */
69 1
    private static function getLastHeader($headers) {
70 1
        if ( is_array($headers) ) {
71
            return end($headers);
72
        }
73 1
        return $headers;
74
    }
75
76
    /**
77
     * Return an array with values from a header like Cache-Control
78
     * e.g. 'max-age=300,public,no-store'
79
     * results in
80
     * [ 'max-age' => '300', 'public' => 'public', 'no-store' => 'no-store' ]
81
     * @param string $header
82
     * @return array
83
     */
84 4
    public static function parseHeader($header)
85
    {
86 4
        $header = (strpos($header, ':')!==false) ? explode(':', $header)[1] : $header;
87 4
        $info   = array_map('trim', explode(',', $header));
88 4
        $header = [];
89 4
        foreach ( $info as $entry ) {
90 4
            $temp               = array_map( 'trim', explode( '=', $entry ));
91 4
            $header[ $temp[0] ] = (isset($temp[1]) ? $temp[1] : $temp[0] );
92 4
        }
93 4
        return $header;
94
    }
95
96
    /**
97
     * Merge multiple occurances of a comma seperated header
98
     * @param array $headers
99
     * @return array
100
     */
101 4
    public static function mergeHeaders( $headers )
102
    {
103 4
        $result = [];
104 4
        if ( is_string($headers) ) {
105 3
            $result = self::parseHeader( $headers );
106 4
        } else foreach ( $headers as $header ) {
107 1
            if (is_string($header)) {
108 1
                $header = self::parseHeader($header);
109 1
            }
110 1
            $result = array_replace_recursive( $result, $header );
111 1
        }
112 4
        return $result;
113
    }
114
115 4
    private static function getCacheControlTime( $header, $private )
116
    {
117 4
        $result    = null;
118 4
        $dontcache = false;
119 4
        foreach ( $header as $key => $value ) {
120
            switch($key) {
121 4
                case 'max-age':
122 4
                case 's-maxage':
123 4
                    if ( isset($result) ) {
124 4
                        $result = min($result, (int) $value);
125 4
                    } else {
126 4
                        $result = (int) $value;
127
                    }
128 4
                break;
129 4
                case 'public':
130 3
                break;
131 3
                case 'private':
132 1
                    if ( !$private ) {
133 1
                        $dontcache = true;
134 1
                    }
135 1
                break;
136 2
                case 'no-cache':
137 2
                case 'no-store':
138 1
                    $dontcache = true;
139 1
                break;
140 1
                case 'must-revalidate':
141 1
                case 'proxy-revalidate':
142
                    $dontcache = true; // FIXME: should return more information than just the cache time instead
0 ignored issues
show
Coding Style introduced by
Comment refers to a FIXME task "should return more information than just the cache time instead"
Loading history...
143
                break;
144 1
                default:
145 1
                break;
146 1
            }
147 4
        }
148 4
        if ( $dontcache ) {
149 2
            $result = 0;
150 2
        }
151 4
        return $result;
152
    }
153
154
    /**
155
     * Parse response headers to determine if and how long you may cache the response. Doesn't understand ETags.
156
     * @param string|string[] $headers Headers string or array as returned by parse()
157
     * @param bool $private Whether to store a private cache or public cache image.
158
     * @return int The number of seconds you may cache this result starting from now.
159
     */
160 5
    public static function parseCacheTime( $headers, $private=true )
161
    {
162 5
        $result = null;
163 5
        if ( is_string($headers) || ( !isset($headers['Cache-Control']) && !isset($headers['Expires']) ) ) {
164 1
            $headers = \arc\http\headers::parse( $headers );
0 ignored issues
show
Bug introduced by
It seems like $headers defined by \arc\http\headers::parse($headers) on line 164 can also be of type array<integer|string,str...ull","Expires":"null"}>; however, arc\http\headers::parse() does only seem to accept string|array<integer,string>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
165 1
        }
166 5
        if ( isset( $headers['Cache-Control'] ) ) {
167 4
            $header = self::mergeHeaders( $headers['Cache-Control'] );
168 4
            $result = self::getCacheControlTime( $header, $private );
169 4
        }
170 5
        if ( !isset($result) && isset( $headers['Expires'] ) ) {
171 1
            $result = strtotime( self::getLastHeader( $headers['Expires'] ) ) - time();
172 1
        }
173 5
        return (int) $result;
174
    }
175
176
}