Completed
Pull Request — v1 (#422)
by
unknown
03:17
created

Frame::getFileLines()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6.0987

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 25
ccs 11
cts 17
cp 0.6471
rs 8.439
cc 5
eloc 13
nc 6
nop 2
crap 6.0987
1
<?php
2
/**
3
 * Whoops - php errors for cool kids
4
 * @author Filipe Dobreira <http://github.com/filp>
5
 */
6
7
namespace Whoops\Exception;
8
9
use InvalidArgumentException;
10
use Serializable;
11
12
class Frame implements Serializable
13
{
14
    /**
15
     * @var array
16
     */
17
    protected $frame;
18
19
    /**
20
     * @var string
21
     */
22
    protected $fileContentsCache;
23
24
    /**
25
     * @var array[]
26
     */
27
    protected $comments = array();
28
29
    /**
30
     * @param array[]
31
     */
32 1
    public function __construct(array $frame)
33
    {
34 1
        $this->frame = $frame;
35 1
    }
36
37
    /**
38
     * @param  bool        $shortened
39
     * @return string|null
40
     */
41 2
    public function getFile($shortened = false)
42
    {
43 2
        if (empty($this->frame['file'])) {
44 1
            return null;
45
        }
46
47 2
        $file = $this->frame['file'];
48
49
        // Check if this frame occurred within an eval().
50
        // @todo: This can be made more reliable by checking if we've entered
51
        // eval() in a previous trace, but will need some more work on the upper
52
        // trace collector(s).
53 2
        if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d|assert) code$/', $file, $matches)) {
54
            $file = $this->frame['file'] = $matches[1];
55
            $this->frame['line'] = (int) $matches[2];
56
        }
57
58 2
        if ($shortened && is_string($file)) {
59
            // Replace the part of the path that all frames have in common, and add 'soft hyphens' for smoother line-breaks.
60
            $dirname = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
61
            $file = str_replace($dirname, "&hellip;", $file);
62
            $file = str_replace("/", "/&shy;", $file);
63
        }
64
65 2
        return $file;
66
    }
67
68
    /**
69
     * @return int|null
70
     */
71 2
    public function getLine()
72
    {
73 2
        return isset($this->frame['line']) ? $this->frame['line'] : null;
74
    }
75
76
    /**
77
     * @return string|null
78
     */
79 2
    public function getClass()
80
    {
81 2
        return isset($this->frame['class']) ? $this->frame['class'] : null;
82
    }
83
84
    /**
85
     * @return string|null
86
     */
87 2
    public function getFunction()
88
    {
89 2
        return isset($this->frame['function']) ? $this->frame['function'] : null;
90
    }
91
92
    /**
93
     * @return array
94
     */
95 1
    public function getArgs()
96
    {
97 1
        return isset($this->frame['args']) ? (array) $this->frame['args'] : array();
98
    }
99
100
    /**
101
     * Returns the full contents of the file for this frame,
102
     * if it's known.
103
     * @return string|null
104
     */
105 1
    public function getFileContents()
106
    {
107 1
        if ($this->fileContentsCache === null && $filePath = $this->getFile()) {
108
            // Leave the stage early when 'Unknown' is passed
109
            // this would otherwise raise an exception when
110
            // open_basedir is enabled.
111 1
            if ($filePath === "Unknown") {
112
                return null;
113
            }
114
115
            // Return null if the file doesn't actually exist.
116 1
            if (!is_file($filePath)) {
117
                return null;
118
            }
119
120 1
            $this->fileContentsCache = file_get_contents($filePath);
121 1
        }
122
123 1
        return $this->fileContentsCache;
124
    }
125
126
    /**
127
     * Adds a comment to this frame, that can be received and
128
     * used by other handlers. For example, the PrettyPage handler
129
     * can attach these comments under the code for each frame.
130
     *
131
     * An interesting use for this would be, for example, code analysis
132
     * & annotations.
133
     *
134
     * @param string $comment
135
     * @param string $context Optional string identifying the origin of the comment
136
     */
137 2
    public function addComment($comment, $context = 'global')
138
    {
139 2
        $this->comments[] = array(
140 2
            'comment' => $comment,
141 2
            'context' => $context,
142
        );
143 2
    }
144
145
    /**
146
     * Returns all comments for this frame. Optionally allows
147
     * a filter to only retrieve comments from a specific
148
     * context.
149
     *
150
     * @param  string  $filter
151
     * @return array[]
152
     */
153 2
    public function getComments($filter = null)
154
    {
155 2
        $comments = $this->comments;
156
157 2
        if ($filter !== null) {
158 1
            $comments = array_filter($comments, function ($c) use ($filter) {
159 1
                return $c['context'] == $filter;
160 1
            });
161 1
        }
162
163 2
        return $comments;
164
    }
165
166
    /**
167
     * Returns the array containing the raw frame data from which
168
     * this Frame object was built
169
     *
170
     * @return array
171
     */
172
    public function getRawFrame()
173
    {
174
        return $this->frame;
175
    }
176
177
    /**
178
     * Returns the contents of the file for this frame as an
179
     * array of lines, and optionally as a clamped range of lines.
180
     *
181
     * NOTE: lines are 0-indexed
182
     *
183
     * @example
184
     *     Get all lines for this file
185
     *     $frame->getFileLines(); // => array( 0 => '<?php', 1 => '...', ...)
186
     * @example
187
     *     Get one line for this file, starting at line 10 (zero-indexed, remember!)
188
     *     $frame->getFileLines(9, 1); // array( 10 => '...', 11 => '...')
189
     *
190
     * @throws InvalidArgumentException if $length is less than or equal to 0
191
     * @param  int                      $start
192
     * @param  int                      $length
193
     * @return string[]|null
194
     */
195 2
    public function getFileLines($start = 0, $length = null)
196
    {
197 2
        if (null !== ($contents = $this->getFileContents())) {
198 2
            $lines = explode("\n", $contents);
199
200
            // Get a subset of lines from $start to $end
201 2
            if ($length !== null) {
202 1
                $start  = (int) $start;
203 1
                $length = (int) $length;
204 1
                if ($start < 0) {
205
                    $start = 0;
206
                }
207
208 1
                if ($length <= 0) {
209
                    throw new InvalidArgumentException(
210
                        "\$length($length) cannot be lower or equal to 0"
211
                    );
212
                }
213
214 1
                $lines = array_slice($lines, $start, $length, true);
215 1
            }
216
217 2
            return $lines;
218
        }
219
    }
220
221
    /**
222
     * Implements the Serializable interface, with special
223
     * steps to also save the existing comments.
224
     *
225
     * @see Serializable::serialize
226
     * @return string
227
     */
228 1
    public function serialize()
229
    {
230 1
        $frame = $this->frame;
231 1
        if (!empty($this->comments)) {
232 1
            $frame['_comments'] = $this->comments;
233 1
        }
234
235 1
        return serialize($frame);
236
    }
237
238
    /**
239
     * Unserializes the frame data, while also preserving
240
     * any existing comment data.
241
     *
242
     * @see Serializable::unserialize
243
     * @param string $serializedFrame
244
     */
245 1
    public function unserialize($serializedFrame)
246
    {
247 1
        $frame = unserialize($serializedFrame);
248
249 1
        if (!empty($frame['_comments'])) {
250 1
            $this->comments = $frame['_comments'];
251 1
            unset($frame['_comments']);
252 1
        }
253
254 1
        $this->frame = $frame;
255 1
    }
256
257
    /**
258
     * Compares Frame against one another
259
     * @param  Frame $frame
260
     * @return bool
261
     */
262 1
    public function equals(Frame $frame)
263
    {
264 1
        if (!$this->getFile() || $this->getFile() === 'Unknown' || !$this->getLine()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getFile() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $this->getLine() of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
265
            return false;
266
        }
267 1
        return $frame->getFile() === $this->getFile() && $frame->getLine() === $this->getLine();
268
    }
269
}
270