FilterBacktrace   A
last analyzed

Complexity

Total Complexity 8

Size/Duplication

Total Lines 136
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 136
rs 10
c 0
b 0
f 0
wmc 8
lcom 1
cbo 1

4 Methods

Rating   Name   Duplication   Size   Complexity  
A filterBacktrace() 0 4 1
B from() 0 31 4
A isClassNameOkay() 0 17 2
B extractFrameDetails() 0 30 1
1
<?php
2
3
/**
4
 * Copyright (c) 2015-present Ganbaro Digital Ltd
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 *   * Redistributions of source code must retain the above copyright
12
 *     notice, this list of conditions and the following disclaimer.
13
 *
14
 *   * Redistributions in binary form must reproduce the above copyright
15
 *     notice, this list of conditions and the following disclaimer in
16
 *     the documentation and/or other materials provided with the
17
 *     distribution.
18
 *
19
 *   * Neither the names of the copyright holders nor the names of his
20
 *     contributors may be used to endorse or promote products derived
21
 *     from this software without specific prior written permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 *
36
 * @category  Libraries
37
 * @package   MissingBits/Traces
38
 * @author    Stuart Herbert <[email protected]>
39
 * @copyright 2015-present Ganbaro Digital Ltd www.ganbarodigital.com
40
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
41
 * @link      http://ganbarodigital.github.io/php-the-missing-bits
42
 */
43
44
namespace GanbaroDigital\MissingBits\TraceInspectors;
45
use GanbaroDigital\MissingBits\TypeInspectors\GetNamespace;
46
47
/**
48
 * find the first entry in a debug_backtrace() array that contains useful
49
 * information, with optional support for skipping over namespaces
50
 * (e.g. skip over namespaces used to enforce robustness)
51
 */
52
class FilterBacktrace
53
{
54
    /**
55
     * find first complete stack frame, optionally skipping over classes
56
     * and namespaces
57
     *
58
     * @param  array $backtrace
59
     *         the debug_backtrace() return value
60
     * @param  array $filterList
61
     *         a list of namespaces and classes to skip over
62
     * @param  int $index
63
     *         how far down the stack do we want to start looking from?
64
     * @return array
65
     */
66
    public function filterBacktrace($backtrace, $filterList = [], $index = 1)
0 ignored issues
show
Coding Style Best Practice introduced by
Please use __construct() instead of a PHP4-style constructor that is named after the class.
Loading history...
67
    {
68
        return self::from($backtrace, $filterList, $index);
69
    }
70
71
    /**
72
     * find first complete stack frame, optionally skipping over classes
73
     * and partial namespaces
74
     *
75
     * @param  array $backtrace
76
     *         the debug_backtrace() return value
77
     * @param  array $filterList
78
     *         a list of partial namespaces and classes to skip over
79
     * @param  int $index
80
     *         how far down the stack do we want to start looking from?
81
     * @return array
82
     */
83
    public static function from($backtrace, $filterList = [], $index = 1)
84
    {
85
        // make sure we're not trying to look beyond the end of the stack trace
86
        $maxIndex = count($backtrace) - 1;
87
        $index = min($index, $maxIndex);
88
89
        // PHP's stack trace is a little esoteric. To find all the details about
90
        // a caller, we have to combine information from two stack frames.
91
        $prevFrame = $backtrace[max($index - 1,0)];
92
93
        // find the first backtrace entry that passes our filters
94
        for ($i = $index; $i <= $maxIndex; $i++) {
95
            // what are we looking at?
96
            $frame = $backtrace[$i];
97
98
            if (!isset($frame['class'])) {
99
                // called from global function
100
                return self::extractFrameDetails($frame, $prevFrame, $i);
101
            }
102
103
            // do we want to skip over this class name?
104
            if (self::isClassNameOkay($frame['class'], $filterList)) {
105
                return self::extractFrameDetails($frame, $prevFrame, $i);
106
            }
107
108
            $prevFrame = $frame;
109
        }
110
111
        // if we get here, then we have run out of places to look
112
        return self::extractFrameDetails($backtrace[1], $backtrace[0], 1);
113
    }
114
115
    /**
116
     * is the given classname NOT in our list to filter out?
117
     *
118
     * @param  string  $className
119
     *         the fully-qualified class name to check
120
     * @param  array $filterList
121
     *         the list of classes and namespaces to filter for
122
     * @return boolean
123
     *         TRUE if the classname is NOT in our list of partial namespaces
124
     *         FALSE otherwise
125
     */
126
    private static function isClassNameOkay($className, $filterList)
127
    {
128
        // the caller may be trying to filter out the class itself, or the
129
        // namespace that the class is inside.
130
        $searchList = [
131
            GetNamespace::from($className),
132
            $className
133
        ];
134
135
        if (empty(array_intersect($filterList, $searchList))) {
136
            return true;
137
        }
138
139
        // if we get here, then this class isn't one that we want to return
140
        // to the caller
141
        return false;
142
    }
143
144
    /**
145
     * extract only the stack frame fields we are interested in
146
     *
147
     * guarantees that the return value contains all four keys, even if they
148
     * are missing from the stack frame
149
     *
150
     * @param  array $frame1
151
     *         a stack frame from `debug_backtrace`
152
     * @param  int $stackIndex
153
     *         which part of the stack is $frame from?
154
     * @return array
155
     *         contains class, function, file, and line
156
     */
157
    private static function extractFrameDetails($frame1, $frame2, $stackIndex)
158
    {
159
        $retval = [
160
            'class' => null,
161
            'function' => null,
162
            'type' => null,
163
            'file' => null,
164
            'line' => null,
165
            'stackIndex' => $stackIndex,
166
        ];
167
168
        $frame1Details = [
169
            'class' => null,
170
            'function' => null,
171
            'type' => null,
172
        ];
173
174
        $frame2Details = [
175
            'file' => null,
176
            'line' => null,
177
        ];
178
179
        // we only want entries from the $frame array that we intend to return
180
        $parts1 = array_intersect_key($frame1, $frame1Details);
181
        $parts2 = array_intersect_key($frame2, $frame2Details);
182
        $retval = array_merge($retval, $parts1, $parts2);
183
184
        // all done
185
        return $retval;
186
    }
187
}
188