Skip to content

Commit 40e3f0b

Browse files
committed
Replace SplStack with plain arrays
In PHP 7.x/8.x arrays outperform SplStack with noticable difference.
1 parent 5092601 commit 40e3f0b

File tree

6 files changed

+126
-93
lines changed

6 files changed

+126
-93
lines changed

src/Context.php

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace JMS\Serializer;
66

77
use JMS\Serializer\Exception\LogicException;
8-
use JMS\Serializer\Exception\RuntimeException;
98
use JMS\Serializer\Exclusion\DepthExclusionStrategy;
109
use JMS\Serializer\Exclusion\DisjunctExclusionStrategy;
1110
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
@@ -50,12 +49,11 @@ abstract class Context
5049
*/
5150
private $initialized = false;
5251

53-
/** @var \SplStack */
54-
private $metadataStack;
52+
/** @var array<ClassMetadata|PropertyMetadata> */
53+
private array $metadataStack = [];
5554

5655
public function __construct()
5756
{
58-
$this->metadataStack = new \SplStack();
5957
}
6058

6159
public function initialize(string $format, VisitorInterface $visitor, GraphNavigatorInterface $navigator, MetadataFactoryInterface $factory): void
@@ -68,7 +66,7 @@ public function initialize(string $format, VisitorInterface $visitor, GraphNavig
6866
$this->visitor = $visitor;
6967
$this->navigator = $navigator;
7068
$this->metadataFactory = $factory;
71-
$this->metadataStack = new \SplStack();
69+
$this->metadataStack = [];
7270

7371
if (isset($this->attributes['groups'])) {
7472
$this->addExclusionStrategy(new GroupsExclusionStrategy($this->attributes['groups']));
@@ -210,35 +208,50 @@ public function getFormat(): string
210208

211209
public function pushClassMetadata(ClassMetadata $metadata): void
212210
{
213-
$this->metadataStack->push($metadata);
211+
$this->metadataStack[] = $metadata;
214212
}
215213

216214
public function pushPropertyMetadata(PropertyMetadata $metadata): void
217215
{
218-
$this->metadataStack->push($metadata);
216+
$this->metadataStack[] = $metadata;
219217
}
220218

221219
public function popPropertyMetadata(): void
222220
{
223-
$metadata = $this->metadataStack->pop();
224-
225-
if (!$metadata instanceof PropertyMetadata) {
226-
throw new RuntimeException('Context metadataStack not working well');
227-
}
221+
array_pop($this->metadataStack);
228222
}
229223

230224
public function popClassMetadata(): void
231225
{
232-
$metadata = $this->metadataStack->pop();
226+
array_pop($this->metadataStack);
227+
}
233228

234-
if (!$metadata instanceof ClassMetadata) {
235-
throw new RuntimeException('Context metadataStack not working well');
236-
}
229+
/**
230+
* Returns the metadata stack count without creating a copy.
231+
*/
232+
public function getMetadataStackSize(): int
233+
{
234+
return \count($this->metadataStack);
235+
}
236+
237+
/**
238+
* Returns the top element of the metadata stack.
239+
*
240+
* @return ClassMetadata|PropertyMetadata
241+
*/
242+
public function getMetadataStackTop()
243+
{
244+
return $this->metadataStack[\count($this->metadataStack) - 1];
237245
}
238246

239-
public function getMetadataStack(): \SplStack
247+
/**
248+
* Returns the metadata stack as an array with LIFO index order (0 = top/most recent).
249+
*
250+
* @return array<ClassMetadata|PropertyMetadata>
251+
*/
252+
public function getMetadataStack(): array
240253
{
241-
return $this->metadataStack;
254+
return array_reverse($this->metadataStack);
242255
}
243256

244257
public function getCurrentPath(): array
@@ -254,7 +267,7 @@ public function getCurrentPath(): array
254267
}
255268
}
256269

257-
return array_reverse($paths);
270+
return $paths;
258271
}
259272

260273
abstract public function getDepth(): int;

src/Exclusion/DepthExclusionStrategy.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ public function shouldSkipProperty(PropertyMetadata $property, Context $context)
2929

3030
private function isTooDeep(Context $context): bool
3131
{
32-
$stack = $context->getMetadataStack();
33-
$currentCount = $stack->count();
32+
$currentCount = $context->getMetadataStackSize();
3433

3534
if ($currentCount === $this->cachedStackCount) {
3635
return $this->cachedResult;
@@ -42,6 +41,8 @@ private function isTooDeep(Context $context): bool
4241
return false;
4342
}
4443

44+
$stack = $context->getMetadataStack();
45+
4546
if (!$this->hasMaxDepthOnStack && !$this->cachedResult && $currentCount > $this->cachedStackCount) {
4647
$delta = $currentCount - $this->cachedStackCount;
4748
$found = false;
@@ -68,7 +69,7 @@ private function isTooDeep(Context $context): bool
6869

6970
// Full scan
7071
$relativeDepth = 0;
71-
$top = $currentCount > 0 ? $stack->top() : null;
72+
$top = $currentCount > 0 ? $stack[0] : null;
7273
$foundMaxDepth = false;
7374

7475
foreach ($stack as $metadata) {

src/Handler/ArrayCollectionHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public function deserializeCollection(
125125
return $elements;
126126
}
127127

128-
$propertyMetadata = $context->getMetadataStack()->top();
128+
$propertyMetadata = $context->getMetadataStackTop();
129129
if (!$propertyMetadata instanceof PropertyMetadata) {
130130
return $elements;
131131
}

src/SerializationContext.php

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@
44

55
namespace JMS\Serializer;
66

7-
use JMS\Serializer\Exception\RuntimeException;
87
use Metadata\MetadataFactoryInterface;
98

109
class SerializationContext extends Context
1110
{
1211
/** @var \SplObjectStorage */
1312
private $visitingSet;
1413

15-
/** @var \SplStack */
16-
private $visitingStack;
14+
/** @var object[] */
15+
private array $visitingStack = [];
1716

1817
/**
1918
* @var string
@@ -35,7 +34,7 @@ public function initialize(string $format, VisitorInterface $visitor, GraphNavig
3534
parent::initialize($format, $visitor, $navigator, $factory);
3635

3736
$this->visitingSet = new \SplObjectStorage();
38-
$this->visitingStack = new \SplStack();
37+
$this->visitingStack = [];
3938
}
4039

4140
/**
@@ -69,7 +68,7 @@ public function startVisiting($object): void
6968
}
7069

7170
$this->visitingSet->offsetSet($object);
72-
$this->visitingStack->push($object);
71+
$this->visitingStack[] = $object;
7372
}
7473

7574
/**
@@ -82,11 +81,7 @@ public function stopVisiting($object): void
8281
}
8382

8483
$this->visitingSet->offsetUnset($object);
85-
$poppedObject = $this->visitingStack->pop();
86-
87-
if ($object !== $poppedObject) {
88-
throw new RuntimeException('Context visitingStack not working well');
89-
}
84+
array_pop($this->visitingStack);
9085
}
9186

9287
/**
@@ -103,15 +98,15 @@ public function isVisiting($object): bool
10398

10499
public function getPath(): ?string
105100
{
101+
if (!$this->visitingStack) {
102+
return null;
103+
}
104+
106105
$path = [];
107106
foreach ($this->visitingStack as $obj) {
108107
$path[] = \get_class($obj);
109108
}
110109

111-
if (!$path) {
112-
return null;
113-
}
114-
115110
return implode(' -> ', $path);
116111
}
117112

@@ -122,15 +117,20 @@ public function getDirection(): int
122117

123118
public function getDepth(): int
124119
{
125-
return $this->visitingStack->count();
120+
return \count($this->visitingStack);
126121
}
127122

128123
public function getObject(): ?object
129124
{
130-
return !$this->visitingStack->isEmpty() ? $this->visitingStack->top() : null;
125+
$n = \count($this->visitingStack);
126+
127+
return $n > 0 ? $this->visitingStack[$n - 1] : null;
131128
}
132129

133-
public function getVisitingStack(): \SplStack
130+
/**
131+
* @return object[]
132+
*/
133+
public function getVisitingStack(): array
134134
{
135135
return $this->visitingStack;
136136
}

0 commit comments

Comments
 (0)