1<?php
2/*
3 * This file is part of the GlobalState package.
4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11namespace SebastianBergmann\GlobalState;
12
13use ReflectionClass;
14use Serializable;
15
16/**
17 * A snapshot of global state.
18 */
19class Snapshot
20{
21    /**
22     * @var Blacklist
23     */
24    private $blacklist;
25
26    /**
27     * @var array
28     */
29    private $globalVariables = array();
30
31    /**
32     * @var array
33     */
34    private $superGlobalArrays = array();
35
36    /**
37     * @var array
38     */
39    private $superGlobalVariables = array();
40
41    /**
42     * @var array
43     */
44    private $staticAttributes = array();
45
46    /**
47     * @var array
48     */
49    private $iniSettings = array();
50
51    /**
52     * @var array
53     */
54    private $includedFiles = array();
55
56    /**
57     * @var array
58     */
59    private $constants = array();
60
61    /**
62     * @var array
63     */
64    private $functions = array();
65
66    /**
67     * @var array
68     */
69    private $interfaces = array();
70
71    /**
72     * @var array
73     */
74    private $classes = array();
75
76    /**
77     * @var array
78     */
79    private $traits = array();
80
81    /**
82     * Creates a snapshot of the current global state.
83     *
84     * @param Blacklist $blacklist
85     * @param bool      $includeGlobalVariables
86     * @param bool      $includeStaticAttributes
87     * @param bool      $includeConstants
88     * @param bool      $includeFunctions
89     * @param bool      $includeClasses
90     * @param bool      $includeInterfaces
91     * @param bool      $includeTraits
92     * @param bool      $includeIniSettings
93     * @param bool      $includeIncludedFiles
94     */
95    public function __construct(Blacklist $blacklist = null, $includeGlobalVariables = true, $includeStaticAttributes = true, $includeConstants = true, $includeFunctions = true, $includeClasses = true, $includeInterfaces = true, $includeTraits = true, $includeIniSettings = true, $includeIncludedFiles = true)
96    {
97        if ($blacklist === null) {
98            $blacklist = new Blacklist;
99        }
100
101        $this->blacklist = $blacklist;
102
103        if ($includeConstants) {
104            $this->snapshotConstants();
105        }
106
107        if ($includeFunctions) {
108            $this->snapshotFunctions();
109        }
110
111        if ($includeClasses || $includeStaticAttributes) {
112            $this->snapshotClasses();
113        }
114
115        if ($includeInterfaces) {
116            $this->snapshotInterfaces();
117        }
118
119        if ($includeGlobalVariables) {
120            $this->setupSuperGlobalArrays();
121            $this->snapshotGlobals();
122        }
123
124        if ($includeStaticAttributes) {
125            $this->snapshotStaticAttributes();
126        }
127
128        if ($includeIniSettings) {
129            $this->iniSettings = ini_get_all(null, false);
130        }
131
132        if ($includeIncludedFiles) {
133            $this->includedFiles = get_included_files();
134        }
135
136        if (function_exists('get_declared_traits')) {
137            $this->traits = get_declared_traits();
138        }
139    }
140
141    /**
142     * @return Blacklist
143     */
144    public function blacklist()
145    {
146        return $this->blacklist;
147    }
148
149    /**
150     * @return array
151     */
152    public function globalVariables()
153    {
154        return $this->globalVariables;
155    }
156
157    /**
158     * @return array
159     */
160    public function superGlobalVariables()
161    {
162        return $this->superGlobalVariables;
163    }
164
165    /**
166     * Returns a list of all super-global variable arrays.
167     *
168     * @return array
169     */
170    public function superGlobalArrays()
171    {
172        return $this->superGlobalArrays;
173    }
174
175    /**
176     * @return array
177     */
178    public function staticAttributes()
179    {
180        return $this->staticAttributes;
181    }
182
183    /**
184     * @return array
185     */
186    public function iniSettings()
187    {
188        return $this->iniSettings;
189    }
190
191    /**
192     * @return array
193     */
194    public function includedFiles()
195    {
196        return $this->includedFiles;
197    }
198
199    /**
200     * @return array
201     */
202    public function constants()
203    {
204        return $this->constants;
205    }
206
207    /**
208     * @return array
209     */
210    public function functions()
211    {
212        return $this->functions;
213    }
214
215    /**
216     * @return array
217     */
218    public function interfaces()
219    {
220        return $this->interfaces;
221    }
222
223    /**
224     * @return array
225     */
226    public function classes()
227    {
228        return $this->classes;
229    }
230
231    /**
232     * @return array
233     */
234    public function traits()
235    {
236        return $this->traits;
237    }
238
239    /**
240     * Creates a snapshot user-defined constants.
241     */
242    private function snapshotConstants()
243    {
244        $constants = get_defined_constants(true);
245
246        if (isset($constants['user'])) {
247            $this->constants = $constants['user'];
248        }
249    }
250
251    /**
252     * Creates a snapshot user-defined functions.
253     */
254    private function snapshotFunctions()
255    {
256        $functions = get_defined_functions();
257
258        $this->functions = $functions['user'];
259    }
260
261    /**
262     * Creates a snapshot user-defined classes.
263     */
264    private function snapshotClasses()
265    {
266        foreach (array_reverse(get_declared_classes()) as $className) {
267            $class = new ReflectionClass($className);
268
269            if (!$class->isUserDefined()) {
270                break;
271            }
272
273            $this->classes[] = $className;
274        }
275
276        $this->classes = array_reverse($this->classes);
277    }
278
279    /**
280     * Creates a snapshot user-defined interfaces.
281     */
282    private function snapshotInterfaces()
283    {
284        foreach (array_reverse(get_declared_interfaces()) as $interfaceName) {
285            $class = new ReflectionClass($interfaceName);
286
287            if (!$class->isUserDefined()) {
288                break;
289            }
290
291            $this->interfaces[] = $interfaceName;
292        }
293
294        $this->interfaces = array_reverse($this->interfaces);
295    }
296
297    /**
298     * Creates a snapshot of all global and super-global variables.
299     */
300    private function snapshotGlobals()
301    {
302        $superGlobalArrays = $this->superGlobalArrays();
303
304        foreach ($superGlobalArrays as $superGlobalArray) {
305            $this->snapshotSuperGlobalArray($superGlobalArray);
306        }
307
308        foreach (array_keys($GLOBALS) as $key) {
309            if ($key != 'GLOBALS' &&
310                !in_array($key, $superGlobalArrays) &&
311                $this->canBeSerialized($GLOBALS[$key]) &&
312                !$this->blacklist->isGlobalVariableBlacklisted($key)) {
313                $this->globalVariables[$key] = unserialize(serialize($GLOBALS[$key]));
314            }
315        }
316    }
317
318    /**
319     * Creates a snapshot a super-global variable array.
320     *
321     * @param $superGlobalArray
322     */
323    private function snapshotSuperGlobalArray($superGlobalArray)
324    {
325        $this->superGlobalVariables[$superGlobalArray] = array();
326
327        if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) {
328            foreach ($GLOBALS[$superGlobalArray] as $key => $value) {
329                $this->superGlobalVariables[$superGlobalArray][$key] = unserialize(serialize($value));
330            }
331        }
332    }
333
334    /**
335     * Creates a snapshot of all static attributes in user-defined classes.
336     */
337    private function snapshotStaticAttributes()
338    {
339        foreach ($this->classes as $className) {
340            $class    = new ReflectionClass($className);
341            $snapshot = array();
342
343            foreach ($class->getProperties() as $attribute) {
344                if ($attribute->isStatic()) {
345                    $name = $attribute->getName();
346
347                    if ($this->blacklist->isStaticAttributeBlacklisted($className, $name)) {
348                        continue;
349                    }
350
351                    $attribute->setAccessible(true);
352                    $value = $attribute->getValue();
353
354                    if ($this->canBeSerialized($value)) {
355                        $snapshot[$name] = unserialize(serialize($value));
356                    }
357                }
358            }
359
360            if (!empty($snapshot)) {
361                $this->staticAttributes[$className] = $snapshot;
362            }
363        }
364    }
365
366    /**
367     * Returns a list of all super-global variable arrays.
368     *
369     * @return array
370     */
371    private function setupSuperGlobalArrays()
372    {
373        $this->superGlobalArrays = array(
374            '_ENV',
375            '_POST',
376            '_GET',
377            '_COOKIE',
378            '_SERVER',
379            '_FILES',
380            '_REQUEST'
381        );
382
383        if (ini_get('register_long_arrays') == '1') {
384            $this->superGlobalArrays = array_merge(
385                $this->superGlobalArrays,
386                array(
387                    'HTTP_ENV_VARS',
388                    'HTTP_POST_VARS',
389                    'HTTP_GET_VARS',
390                    'HTTP_COOKIE_VARS',
391                    'HTTP_SERVER_VARS',
392                    'HTTP_POST_FILES'
393                )
394            );
395        }
396    }
397
398    /**
399     * @param  mixed $variable
400     * @return bool
401     * @todo   Implement this properly
402     */
403    private function canBeSerialized($variable)
404    {
405        if (!is_object($variable)) {
406            return !is_resource($variable);
407        }
408
409        if ($variable instanceof \stdClass) {
410            return true;
411        }
412
413        $class = new ReflectionClass($variable);
414
415        do {
416            if ($class->isInternal()) {
417                return $variable instanceof Serializable;
418            }
419        } while ($class = $class->getParentClass());
420
421        return true;
422    }
423}
424