Skip to content
Snippets Groups Projects
Commit 008a7af0 authored by Oliver Hader's avatar Oliver Hader Committed by Oliver Hader
Browse files

[TASK] Introduce Map data-structure

Unfortunately PHP emphasizes "weak" over "map" in their new PHP 8
data-structure `\WeakMap`. As a result it cannot be passed to other
functions that would enrich an existing `\WeakMap` - since objects
created in that function scope would not exist outside and thus
directly trigger garbage collection of `\WeakMap`.

`\SplObjectStorage` has a strange behavior when using an iteration
like `foreach ($map as $key => $value)` - the `$value` is actually
the `$key` for BC reasons.

As a substitute, `\TYPO3\CMS\Core\Type\Map` is introduced which has a
similar behavior and got an additional `Map::fromEntries()` factory.
It acts as a wrapper of `\SplObjectStorage` with reduced features.

Example:

$map = new \TYPO3\CMS\Core\Type\Map();
$key = new \stdClass();
$value = new \stdClass();
$map[$key] = $value;

foreach ($map as $key => $value) { ... }

Resolves: #100168
Releases: main
Change-Id: I5c26dc4aa9b4679112a27bd4cbebcfbe0899b094
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/78127


Tested-by: default avatarBenni Mack <benni@typo3.org>
Reviewed-by: default avatarOliver Hader <oliver.hader@typo3.org>
Tested-by: default avatarOliver Hader <oliver.hader@typo3.org>
Reviewed-by: default avatarBenni Mack <benni@typo3.org>
Tested-by: default avatarcore-ci <typo3@b13.com>
Tested-by: default avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: default avatarAndreas Fernandez <a.fernandez@scripting-base.de>
parent 3be034e6
Branches
Tags
No related merge requests found
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Type;
/**
* Map implementation that supports objects as keys.
*
* PHP's \WeakMap is not an option in case object keys are created and assigned
* in an encapsulated scope (like passing a map to a function to enrich it). In
* case the original object is not referenced anymore, it also will vanish from
* a \WeakMap, when used as key (see https://www.php.net/manual/class.weakmap.php).
*
* PHP's \SplObjectStorage has a strange behavior when using an iteration like
* `foreach ($map as $key => $value)` - the `$value` is actually the `$key` for
* BC reasons (see https://bugs.php.net/bug.php?id=49967).
*
* This individual implementation works around the "weak" behavior of \WeakMap
* and the iteration issue with `foreach` of `\SplObjectStorage` by acting as
* a wrapper for `\SplObjectStorage` with reduced features.
*
* Example:
* ```
* $map = new \TYPO3\CMS\Core\Type\Map();
* $key = new \stdClass();
* $value = new \stdClass();
* $map[$key] = $value;
*
* foreach ($map as $key => $value) { ... }
* ```
*/
final class Map implements \ArrayAccess, \Countable, \Iterator
{
private \SplObjectStorage $storage;
/**
* @template E array{0:mixed, 1:mixed}
* @param list<E> $entries
*/
public static function fromEntries(array ...$entries): self
{
$map = new self();
foreach ($entries as $entry) {
$map[$entry[0]] = $entry[1];
}
return $map;
}
public function __construct()
{
$this->storage = new \SplObjectStorage();
}
public function key(): mixed
{
return $this->storage->current();
}
public function current(): mixed
{
return $this->storage->getInfo();
}
public function next(): void
{
$this->storage->next();
}
public function rewind(): void
{
$this->storage->rewind();
}
public function valid(): bool
{
return $this->storage->valid();
}
public function offsetExists(mixed $offset): bool
{
return $this->storage->offsetExists($offset);
}
public function offsetGet(mixed $offset): mixed
{
return $this->storage->offsetGet($offset);
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->storage->offsetSet($offset, $value);
}
public function offsetUnset(mixed $offset): void
{
$this->storage->offsetUnset($offset);
}
public function count(): int
{
return count($this->storage);
}
}
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Tests\Unit\Type;
use TYPO3\CMS\Core\Type\Map;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
class MapTest extends UnitTestCase
{
/**
* @test
*/
public function mapIsArrayAccessible(): void
{
$aKey = new \stdClass();
$aValue = new \stdClass();
$bKey = new \stdClass();
$bValue = new \stdClass();
$map = new Map();
$map[$aKey] = $aValue;
$map[$bKey] = $bValue;
self::assertInstanceOf(Map::class, $map);
self::assertCount(2, $map);
self::assertSame($aValue, $map[$aKey]);
self::assertSame($bValue, $map[$bKey]);
}
/**
* @test
*/
public function mapKeyCanBeUnset(): void
{
$aKey = new \stdClass();
$aValue = new \stdClass();
$bKey = new \stdClass();
$bValue = new \stdClass();
$map = new Map();
$map[$aKey] = $aValue;
$map[$bKey] = $bValue;
unset($map[$bKey]);
self::assertCount(1, $map);
self::assertFalse(isset($map[$bKey]));
}
/**
* @test
*/
public function mapCanBeIterated(): void
{
$aKey = new \stdClass();
$aValue = new \stdClass();
$bKey = new \stdClass();
$bValue = new \stdClass();
$map = new Map();
$map[$aKey] = $aValue;
$map[$bKey] = $bValue;
$entries = [];
foreach ($map as $key => $value) {
$entries[] = [$key, $value];
}
$expectation = [
[$aKey, $aValue],
[$bKey, $bValue],
];
self::assertSame($expectation, $entries);
}
/**
* @test
*/
public function mapIsCreatedFromEntries(): void
{
$aKey = new \stdClass();
$aValue = new \stdClass();
$bKey = new \stdClass();
$bValue = new \stdClass();
$map = Map::fromEntries(
[$aKey, $aValue],
[$bKey, $bValue],
);
self::assertCount(2, $map);
self::assertSame($aValue, $map[$aKey]);
self::assertSame($bValue, $map[$bKey]);
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment