Completed
Pull Request — master (#599)
by Dmitry
02:06
created

Frame::getFileContents()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5

Importance

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