Skip to content

Instantly share code, notes, and snippets.

@pscheit
Created May 9, 2025 03:23
Show Gist options
  • Save pscheit/8c36380e09c566baf8603eb14ffec74a to your computer and use it in GitHub Desktop.
Save pscheit/8c36380e09c566baf8603eb14ffec74a to your computer and use it in GitHub Desktop.
<?php declare(strict_types=1);
namespace YAY;
use Countable;
use IteratorAggregate;
use Traversable;
use Webmozart\Assert\Assert;
/**
* @template T of object
* @implements IteratorAggregate<string, T>
*/
final class Map implements IteratorAggregate, Countable
{
/**
* @var array<string, T>
*/
private array $objects;
/**
* @param array<T> $objects
* @param class-string<T> $type
* @param \Closure(T $object): non-empty-string $indexer
*/
private function __construct(
private readonly string $type,
private readonly \Closure $indexer,
array $objects,
) {
$this->objects = [];
foreach ($objects as $object) {
$this->add($object);
}
}
/**
* @template TCreate of object
* @param array<TCreate> $objects
* @param class-string<TCreate> $type
* @return Map<TCreate>
*/
public static function byProperty(string $propertyName, string $type, array $objects): self
{
$indexer = function (object $object) use ($type, $propertyName): string {
$value = $object->{$propertyName};
if (!is_string($value) && !is_int($value)) {
throw new \LogicException('Cannot use property ' . $propertyName . ' of ' . $object::class . ' as index, because I must be able to cast it to string.');
}
$stringedValue = (string) $value;
if ($stringedValue === "") {
throw new \RuntimeException('The indexer did not index an object correctly: ' . $type . '. The string was empty.');
}
return $stringedValue;
};
return new self($type, $indexer, $objects);
}
/**
* @template TCreate of object
* @param array<TCreate> $objects
* @param class-string<TCreate> $type
* @return Map<TCreate>
*/
public static function byMethod(string $methodName, string $type, array $objects): self
{
$indexer = function (object $object) use ($type, $methodName): string {
$value = $object->{$methodName}();
if (!is_string($value) && !is_int($value)) {
throw new \LogicException('Cannot use return value from method ' . $methodName . ' of ' . $object::class . ' as index, because I must be able to cast it to string.');
}
$stringedValue = (string) $value;
if ($stringedValue === "") {
throw new \RuntimeException('The indexer did not index an object correctly: ' . $type . '. The string was empty.');
}
return $stringedValue;
};
return new self($type, $indexer, $objects);
}
public function has(string $key): bool
{
return array_key_exists($key, $this->objects);
}
/**
* @param T $object
*/
public function add(object $object): void
{
Assert::isInstanceOf($object, $this->type); // @phpstan-ignore staticMethod.alreadyNarrowedType
$index = ($this->indexer)($object);
$this->objects[$index] = $object;
}
/**
* @return T
*/
public function get(string $key)
{
if (!$this->has($key)) {
throw new \OutOfBoundsException('Object not found by indexed ' . $key);
}
return $this->objects[$key];
}
public function getIterator(): Traversable
{
return new \ArrayIterator($this->objects);
}
public function count(): int
{
return count($this->objects);
}
public function remove(string $key): void
{
unset($this->objects[$key]);
}
}
@pscheit
Copy link
Author

pscheit commented May 9, 2025

Makes code so much more readable than array_key_exists, isset and using plan arrays:

    /**
     * @param list<N26CsvRow> $csvs
     * @return list<TransactionAggregate>
     */
    public function syncCsvTransactions(array $csvs, TenantIdentifier $tenantIdentifier, ZonedDateTime $at): array
    {
        $index = $this->repository->getIndex();

        $transactions = [];
        foreach ($csvs as $csv) {
            $hash = $csv->hash();
            
            if ($index->has($hash)) {
                $this->logger->notice($csv . ', is already imported.');
                continue;
            }

            $transactions[] = $transaction = TransactionAggregate::createFromN26CsvRow(
                TransactionIdentifier::create(),
                $tenantIdentifier,
                $csv,
                $at,
            );

            $this->repository->save($transaction);
        }

        return $transactions;
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment