laudis-technologies/neo4j-php-client

View on GitHub
src/Types/ArrayList.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

/*
 * This file is part of the Neo4j PHP Client and Driver package.
 *
 * (c) Nagels <https://nagels.tech>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Laudis\Neo4j\Types;

use AppendIterator;
use ArrayIterator;
use Generator;

use function is_array;
use function is_callable;
use function is_iterable;

use Laudis\Neo4j\Exception\RuntimeTypeException;
use Laudis\Neo4j\TypeCaster;
use OutOfBoundsException;

/**
 * An immutable ordered sequence of items.
 *
 * @template TValue
 *
 * @extends AbstractCypherSequence<TValue, int>
 */
class ArrayList extends AbstractCypherSequence
{
    /**
     * @param iterable<mixed, TValue>|callable():Generator<mixed, TValue> $iterable
     *
     * @psalm-mutation-free
     */
    public function __construct($iterable = [])
    {
        if (is_array($iterable)) {
            $iterable = new ArrayIterator($iterable);
        }

        $this->generator = static function () use ($iterable): Generator {
            $i = 0;
            /** @var Generator<mixed, TValue> $it */
            $it = is_callable($iterable) ? $iterable() : $iterable;
            foreach ($it as $value) {
                yield $i => $value;
                ++$i;
            }
        };
    }

    /**
     * @template Value
     *
     * @param callable():(\Generator<mixed, Value>) $operation
     *
     * @return static<Value>
     *
     * @psalm-mutation-free
     */
    protected function withOperation($operation): AbstractCypherSequence
    {
        /** @psalm-suppress UnsafeInstantiation */
        return new static($operation);
    }

    /**
     * Returns the first element in the sequence.
     *
     * @return TValue
     */
    public function first()
    {
        foreach ($this as $value) {
            return $value;
        }

        throw new OutOfBoundsException('Cannot grab first element of an empty list');
    }

    /**
     * Returns the last element in the sequence.
     *
     * @return TValue
     */
    public function last()
    {
        if ($this->isEmpty()) {
            throw new OutOfBoundsException('Cannot grab last element of an empty list');
        }

        $array = $this->toArray();

        return $array[count($array) - 1];
    }

    /**
     * @template NewValue
     *
     * @param iterable<mixed, NewValue> $values
     *
     * @return static<TValue|NewValue>
     *
     * @psalm-mutation-free
     */
    public function merge($values): ArrayList
    {
        return $this->withOperation(function () use ($values): Generator {
            $iterator = new AppendIterator();

            $iterator->append($this);
            $iterator->append(new self($values));

            yield from $iterator;
        });
    }

    /**
     * Gets the nth element in the list.
     *
     * @throws OutOfBoundsException
     *
     * @return TValue
     */
    public function get(int $key)
    {
        return $this->offsetGet($key);
    }

    public function getAsString(int $key): string
    {
        $value = $this->get($key);
        $tbr = TypeCaster::toString($value);
        if ($tbr === null) {
            throw new RuntimeTypeException($value, 'string');
        }

        return $tbr;
    }

    public function getAsInt(int $key): int
    {
        $value = $this->get($key);
        $tbr = TypeCaster::toInt($value);
        if ($tbr === null) {
            throw new RuntimeTypeException($value, 'int');
        }

        return $tbr;
    }

    public function getAsFloat(int $key): float
    {
        $value = $this->get($key);
        $tbr = TypeCaster::toFloat($value);
        if ($tbr === null) {
            throw new RuntimeTypeException($value, 'float');
        }

        return $tbr;
    }

    public function getAsBool(int $key): bool
    {
        $value = $this->get($key);
        $tbr = TypeCaster::toBool($value);
        if ($tbr === null) {
            throw new RuntimeTypeException($value, 'bool');
        }

        return $tbr;
    }

    /**
     * @return null
     */
    public function getAsNull(int $key)
    {
        /** @psalm-suppress UnusedMethodCall */
        $this->get($key);

        return TypeCaster::toNull();
    }

    /**
     * @template U
     *
     * @param class-string<U> $class
     *
     * @return U
     */
    public function getAsObject(int $key, string $class): object
    {
        $value = $this->get($key);
        $tbr = TypeCaster::toClass($value, $class);
        if ($tbr === null) {
            throw new RuntimeTypeException($value, $class);
        }

        return $tbr;
    }

    /**
     * @return Map<mixed>
     */
    public function getAsMap(int $key): Map
    {
        $value = $this->get($key);
        if (!is_iterable($value)) {
            throw new RuntimeTypeException($value, Map::class);
        }

        /** @psalm-suppress MixedArgumentTypeCoercion */
        return new Map($value);
    }

    /**
     * @return ArrayList<mixed>
     */
    public function getAsArrayList(int $key): ArrayList
    {
        $value = $this->get($key);
        if (!is_iterable($value)) {
            throw new RuntimeTypeException($value, self::class);
        }

        /** @psalm-suppress MixedArgumentTypeCoercion */
        return new ArrayList($value);
    }

    /**
     * @template Value
     *
     * @param iterable<mixed, Value> $iterable
     *
     * @return static<Value>
     *
     * @pure
     */
    public static function fromIterable(iterable $iterable): ArrayList
    {
        /** @psalm-suppress UnsafeInstantiation */
        return new static($iterable);
    }
}