1<?php 2 3declare(strict_types=1); 4 5namespace JMS\Serializer; 6 7use JMS\Serializer\Exception\LogicException; 8use JMS\Serializer\Exception\RuntimeException; 9use JMS\Serializer\Exclusion\DepthExclusionStrategy; 10use JMS\Serializer\Exclusion\DisjunctExclusionStrategy; 11use JMS\Serializer\Exclusion\ExclusionStrategyInterface; 12use JMS\Serializer\Exclusion\GroupsExclusionStrategy; 13use JMS\Serializer\Exclusion\VersionExclusionStrategy; 14use JMS\Serializer\Metadata\ClassMetadata; 15use JMS\Serializer\Metadata\PropertyMetadata; 16use Metadata\MetadataFactory; 17use Metadata\MetadataFactoryInterface; 18 19abstract class Context 20{ 21 /** 22 * @var array 23 */ 24 private $attributes = []; 25 26 /** 27 * @var string 28 */ 29 private $format; 30 31 /** 32 * @var VisitorInterface 33 */ 34 private $visitor; 35 36 /** 37 * @var GraphNavigatorInterface 38 */ 39 private $navigator; 40 41 /** 42 * @var MetadataFactory 43 */ 44 private $metadataFactory; 45 46 /** @var DisjunctExclusionStrategy */ 47 private $exclusionStrategy; 48 49 /** 50 * @var bool 51 */ 52 private $initialized = false; 53 54 /** @var \SplStack */ 55 private $metadataStack; 56 57 public function __construct() 58 { 59 } 60 61 public function initialize(string $format, VisitorInterface $visitor, GraphNavigatorInterface $navigator, MetadataFactoryInterface $factory): void 62 { 63 if ($this->initialized) { 64 throw new LogicException('This context was already initialized, and cannot be re-used.'); 65 } 66 67 $this->format = $format; 68 $this->visitor = $visitor; 69 $this->navigator = $navigator; 70 $this->metadataFactory = $factory; 71 $this->metadataStack = new \SplStack(); 72 73 if (isset($this->attributes['groups'])) { 74 $this->addExclusionStrategy(new GroupsExclusionStrategy($this->attributes['groups'])); 75 } 76 77 if (isset($this->attributes['version'])) { 78 $this->addExclusionStrategy(new VersionExclusionStrategy($this->attributes['version'])); 79 } 80 81 if (!empty($this->attributes['max_depth_checks'])) { 82 $this->addExclusionStrategy(new DepthExclusionStrategy()); 83 } 84 85 $this->initialized = true; 86 } 87 88 public function getMetadataFactory(): MetadataFactoryInterface 89 { 90 return $this->metadataFactory; 91 } 92 93 public function getVisitor(): VisitorInterface 94 { 95 return $this->visitor; 96 } 97 98 public function getNavigator(): GraphNavigatorInterface 99 { 100 return $this->navigator; 101 } 102 103 public function getExclusionStrategy(): ?ExclusionStrategyInterface 104 { 105 return $this->exclusionStrategy; 106 } 107 108 /** 109 * @return mixed 110 */ 111 public function getAttribute(string $key) 112 { 113 return $this->attributes[$key]; 114 } 115 116 public function hasAttribute(string $key): bool 117 { 118 return isset($this->attributes[$key]); 119 } 120 121 /** 122 * @param mixed $value 123 */ 124 public function setAttribute(string $key, $value): self 125 { 126 $this->assertMutable(); 127 $this->attributes[$key] = $value; 128 129 return $this; 130 } 131 132 private function assertMutable(): void 133 { 134 if (!$this->initialized) { 135 return; 136 } 137 138 throw new LogicException('This context was already initialized and is immutable; you cannot modify it anymore.'); 139 } 140 141 public function addExclusionStrategy(ExclusionStrategyInterface $strategy): self 142 { 143 $this->assertMutable(); 144 145 if (null === $this->exclusionStrategy) { 146 $this->exclusionStrategy = $strategy; 147 return $this; 148 } 149 150 if ($this->exclusionStrategy instanceof DisjunctExclusionStrategy) { 151 $this->exclusionStrategy->addStrategy($strategy); 152 return $this; 153 } 154 155 $this->exclusionStrategy = new DisjunctExclusionStrategy([ 156 $this->exclusionStrategy, 157 $strategy, 158 ]); 159 160 return $this; 161 } 162 163 public function setVersion(string $version): self 164 { 165 $this->attributes['version'] = $version; 166 167 return $this; 168 } 169 170 /** 171 * @param array|string $groups 172 */ 173 public function setGroups($groups): self 174 { 175 if (empty($groups)) { 176 throw new LogicException('The groups must not be empty.'); 177 } 178 179 $this->attributes['groups'] = (array) $groups; 180 181 return $this; 182 } 183 184 public function enableMaxDepthChecks(): self 185 { 186 $this->attributes['max_depth_checks'] = true; 187 188 return $this; 189 } 190 191 public function getFormat(): string 192 { 193 return $this->format; 194 } 195 196 public function pushClassMetadata(ClassMetadata $metadata): void 197 { 198 $this->metadataStack->push($metadata); 199 } 200 201 public function pushPropertyMetadata(PropertyMetadata $metadata): void 202 { 203 $this->metadataStack->push($metadata); 204 } 205 206 public function popPropertyMetadata(): void 207 { 208 $metadata = $this->metadataStack->pop(); 209 210 if (!$metadata instanceof PropertyMetadata) { 211 throw new RuntimeException('Context metadataStack not working well'); 212 } 213 } 214 215 public function popClassMetadata(): void 216 { 217 $metadata = $this->metadataStack->pop(); 218 219 if (!$metadata instanceof ClassMetadata) { 220 throw new RuntimeException('Context metadataStack not working well'); 221 } 222 } 223 224 public function getMetadataStack(): \SplStack 225 { 226 return $this->metadataStack; 227 } 228 229 /** 230 * @return array 231 */ 232 public function getCurrentPath(): array 233 { 234 if (!$this->metadataStack) { 235 return []; 236 } 237 238 $paths = []; 239 foreach ($this->metadataStack as $metadata) { 240 if ($metadata instanceof PropertyMetadata) { 241 array_unshift($paths, $metadata->name); 242 } 243 } 244 245 return $paths; 246 } 247 248 abstract public function getDepth(): int; 249 250 abstract public function getDirection(): int; 251} 252