Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@14: declare(strict_types=1); Chris@14: Chris@0: namespace SebastianBergmann\GlobalState; Chris@0: Chris@0: use ReflectionClass; Chris@0: use Serializable; Chris@0: Chris@0: /** Chris@0: * A snapshot of global state. Chris@0: */ Chris@0: class Snapshot Chris@0: { Chris@0: /** Chris@0: * @var Blacklist Chris@0: */ Chris@0: private $blacklist; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $globalVariables = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $superGlobalArrays = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $superGlobalVariables = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $staticAttributes = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $iniSettings = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $includedFiles = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $constants = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $functions = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $interfaces = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $classes = []; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@14: private $traits = []; Chris@0: Chris@0: /** Chris@0: * Creates a snapshot of the current global state. Chris@0: */ Chris@14: public function __construct(Blacklist $blacklist = null, bool $includeGlobalVariables = true, bool $includeStaticAttributes = true, bool $includeConstants = true, bool $includeFunctions = true, bool $includeClasses = true, bool $includeInterfaces = true, bool $includeTraits = true, bool $includeIniSettings = true, bool $includeIncludedFiles = true) Chris@0: { Chris@0: if ($blacklist === null) { Chris@0: $blacklist = new Blacklist; Chris@0: } Chris@0: Chris@0: $this->blacklist = $blacklist; Chris@0: Chris@0: if ($includeConstants) { Chris@0: $this->snapshotConstants(); Chris@0: } Chris@0: Chris@0: if ($includeFunctions) { Chris@0: $this->snapshotFunctions(); Chris@0: } Chris@0: Chris@0: if ($includeClasses || $includeStaticAttributes) { Chris@0: $this->snapshotClasses(); Chris@0: } Chris@0: Chris@0: if ($includeInterfaces) { Chris@0: $this->snapshotInterfaces(); Chris@0: } Chris@0: Chris@0: if ($includeGlobalVariables) { Chris@0: $this->setupSuperGlobalArrays(); Chris@0: $this->snapshotGlobals(); Chris@0: } Chris@0: Chris@0: if ($includeStaticAttributes) { Chris@0: $this->snapshotStaticAttributes(); Chris@0: } Chris@0: Chris@0: if ($includeIniSettings) { Chris@14: $this->iniSettings = \ini_get_all(null, false); Chris@0: } Chris@0: Chris@0: if ($includeIncludedFiles) { Chris@14: $this->includedFiles = \get_included_files(); Chris@0: } Chris@0: Chris@14: $this->traits = \get_declared_traits(); Chris@0: } Chris@0: Chris@14: public function blacklist(): Blacklist Chris@0: { Chris@0: return $this->blacklist; Chris@0: } Chris@0: Chris@14: public function globalVariables(): array Chris@0: { Chris@0: return $this->globalVariables; Chris@0: } Chris@0: Chris@14: public function superGlobalVariables(): array Chris@0: { Chris@0: return $this->superGlobalVariables; Chris@0: } Chris@0: Chris@14: public function superGlobalArrays(): array Chris@0: { Chris@0: return $this->superGlobalArrays; Chris@0: } Chris@0: Chris@14: public function staticAttributes(): array Chris@0: { Chris@0: return $this->staticAttributes; Chris@0: } Chris@0: Chris@14: public function iniSettings(): array Chris@0: { Chris@0: return $this->iniSettings; Chris@0: } Chris@0: Chris@14: public function includedFiles(): array Chris@0: { Chris@0: return $this->includedFiles; Chris@0: } Chris@0: Chris@14: public function constants(): array Chris@0: { Chris@0: return $this->constants; Chris@0: } Chris@0: Chris@14: public function functions(): array Chris@0: { Chris@0: return $this->functions; Chris@0: } Chris@0: Chris@14: public function interfaces(): array Chris@0: { Chris@0: return $this->interfaces; Chris@0: } Chris@0: Chris@14: public function classes(): array Chris@0: { Chris@0: return $this->classes; Chris@0: } Chris@0: Chris@14: public function traits(): array Chris@0: { Chris@0: return $this->traits; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a snapshot user-defined constants. Chris@0: */ Chris@0: private function snapshotConstants() Chris@0: { Chris@14: $constants = \get_defined_constants(true); Chris@0: Chris@0: if (isset($constants['user'])) { Chris@0: $this->constants = $constants['user']; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a snapshot user-defined functions. Chris@0: */ Chris@0: private function snapshotFunctions() Chris@0: { Chris@14: $functions = \get_defined_functions(); Chris@0: Chris@0: $this->functions = $functions['user']; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a snapshot user-defined classes. Chris@0: */ Chris@0: private function snapshotClasses() Chris@0: { Chris@14: foreach (\array_reverse(\get_declared_classes()) as $className) { Chris@0: $class = new ReflectionClass($className); Chris@0: Chris@0: if (!$class->isUserDefined()) { Chris@0: break; Chris@0: } Chris@0: Chris@0: $this->classes[] = $className; Chris@0: } Chris@0: Chris@14: $this->classes = \array_reverse($this->classes); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a snapshot user-defined interfaces. Chris@0: */ Chris@0: private function snapshotInterfaces() Chris@0: { Chris@14: foreach (\array_reverse(\get_declared_interfaces()) as $interfaceName) { Chris@0: $class = new ReflectionClass($interfaceName); Chris@0: Chris@0: if (!$class->isUserDefined()) { Chris@0: break; Chris@0: } Chris@0: Chris@0: $this->interfaces[] = $interfaceName; Chris@0: } Chris@0: Chris@14: $this->interfaces = \array_reverse($this->interfaces); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a snapshot of all global and super-global variables. Chris@0: */ Chris@0: private function snapshotGlobals() Chris@0: { Chris@0: $superGlobalArrays = $this->superGlobalArrays(); Chris@0: Chris@0: foreach ($superGlobalArrays as $superGlobalArray) { Chris@0: $this->snapshotSuperGlobalArray($superGlobalArray); Chris@0: } Chris@0: Chris@14: foreach (\array_keys($GLOBALS) as $key) { Chris@0: if ($key != 'GLOBALS' && Chris@14: !\in_array($key, $superGlobalArrays) && Chris@0: $this->canBeSerialized($GLOBALS[$key]) && Chris@0: !$this->blacklist->isGlobalVariableBlacklisted($key)) { Chris@14: $this->globalVariables[$key] = \unserialize(\serialize($GLOBALS[$key])); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a snapshot a super-global variable array. Chris@0: */ Chris@14: private function snapshotSuperGlobalArray(string $superGlobalArray) Chris@0: { Chris@14: $this->superGlobalVariables[$superGlobalArray] = []; Chris@0: Chris@14: if (isset($GLOBALS[$superGlobalArray]) && \is_array($GLOBALS[$superGlobalArray])) { Chris@0: foreach ($GLOBALS[$superGlobalArray] as $key => $value) { Chris@14: $this->superGlobalVariables[$superGlobalArray][$key] = \unserialize(\serialize($value)); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a snapshot of all static attributes in user-defined classes. Chris@0: */ Chris@0: private function snapshotStaticAttributes() Chris@0: { Chris@0: foreach ($this->classes as $className) { Chris@0: $class = new ReflectionClass($className); Chris@14: $snapshot = []; Chris@0: Chris@0: foreach ($class->getProperties() as $attribute) { Chris@0: if ($attribute->isStatic()) { Chris@0: $name = $attribute->getName(); Chris@0: Chris@0: if ($this->blacklist->isStaticAttributeBlacklisted($className, $name)) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: $attribute->setAccessible(true); Chris@0: $value = $attribute->getValue(); Chris@0: Chris@0: if ($this->canBeSerialized($value)) { Chris@14: $snapshot[$name] = \unserialize(\serialize($value)); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (!empty($snapshot)) { Chris@0: $this->staticAttributes[$className] = $snapshot; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a list of all super-global variable arrays. Chris@0: */ Chris@0: private function setupSuperGlobalArrays() Chris@0: { Chris@14: $this->superGlobalArrays = [ Chris@0: '_ENV', Chris@0: '_POST', Chris@0: '_GET', Chris@0: '_COOKIE', Chris@0: '_SERVER', Chris@0: '_FILES', Chris@0: '_REQUEST' Chris@14: ]; Chris@0: Chris@14: if (\ini_get('register_long_arrays') == '1') { Chris@14: $this->superGlobalArrays = \array_merge( Chris@0: $this->superGlobalArrays, Chris@14: [ Chris@0: 'HTTP_ENV_VARS', Chris@0: 'HTTP_POST_VARS', Chris@0: 'HTTP_GET_VARS', Chris@0: 'HTTP_COOKIE_VARS', Chris@0: 'HTTP_SERVER_VARS', Chris@0: 'HTTP_POST_FILES' Chris@14: ] Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@14: * @todo Implement this properly Chris@0: */ Chris@14: private function canBeSerialized($variable): bool Chris@0: { Chris@14: if (!\is_object($variable)) { Chris@14: return !\is_resource($variable); Chris@0: } Chris@0: Chris@0: if ($variable instanceof \stdClass) { Chris@0: return true; Chris@0: } Chris@0: Chris@0: $class = new ReflectionClass($variable); Chris@0: Chris@0: do { Chris@0: if ($class->isInternal()) { Chris@0: return $variable instanceof Serializable; Chris@0: } Chris@0: } while ($class = $class->getParentClass()); Chris@0: Chris@0: return true; Chris@0: } Chris@0: }