<?php
/*
* This file is part of Chevere.
*
* (c) Rodolfo Berrios <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chevere\Parameter;
use BadMethodCallException;
use Chevere\DataStructure\Interfaces\VectorInterface;
use Chevere\DataStructure\Map;
use Chevere\DataStructure\Traits\MapTrait;
use Chevere\DataStructure\Vector;
use Chevere\Parameter\Interfaces\ArgumentsInterface;
use Chevere\Parameter\Interfaces\ParameterCastInterface;
use Chevere\Parameter\Interfaces\ParameterInterface;
use Chevere\Parameter\Interfaces\ParametersInterface;
use InvalidArgumentException;
use OverflowException;
use function Chevere\Message\message;
final class Parameters implements ParametersInterface
{
/**
* @template-use MapTrait<ParameterInterface>
*/
use MapTrait;
/**
* @var Map<ParameterInterface>
*/
private Map $map;
/**
* @var Vector<string>
*/
private VectorInterface $requiredKeys;
/**
* @var Vector<string>
*/
private VectorInterface $optionalKeys;
private int $optionalMinimum = 0;
private bool $isVariadic = false;
/**
* @param ParameterInterface $parameter Required parameters
*/
public function __construct(ParameterInterface ...$parameter)
{
$this->map = new Map();
$this->requiredKeys = new Vector();
$this->optionalKeys = new Vector();
foreach ($parameter as $name => $item) {
$name = strval($name);
$this->addProperty('requiredKeys', $name, $item);
}
}
public function __invoke(mixed ...$argument): ArgumentsInterface
{
return new Arguments($this, $argument);
}
public function withIsVariadic(bool $flag): ParametersInterface
{
$new = clone $this;
$new->isVariadic = $flag;
return $new;
}
public function isVariadic(): bool
{
return $this->isVariadic;
}
public function withRequired(string $name, ParameterInterface $parameter): ParametersInterface
{
$new = clone $this;
$new->addProperty('requiredKeys', $name, $parameter);
return $new;
}
public function withOptional(string $name, ParameterInterface $parameter): ParametersInterface
{
$new = clone $this;
$new->addProperty('optionalKeys', $name, $parameter);
return $new;
}
public function withMakeOptional(string ...$name): ParametersInterface
{
$new = clone $this;
if ($name === []) {
$name = $this->requiredKeys->toArray();
}
foreach ($name as $key) {
if (! $new->requiredKeys->contains($key)) {
throw new InvalidArgumentException(
(string) message(
'Parameter `%name%` is not required',
name: $key
)
);
}
$parameter = $new->get($key);
$new->remove($key);
$new->addProperty('optionalKeys', $key, $parameter);
}
return $new;
}
public function withMakeRequired(string ...$name): ParametersInterface
{
$new = clone $this;
if ($name === []) {
$name = $this->optionalKeys->toArray();
}
foreach ($name as $key) {
if (! $new->optionalKeys()->contains($key)) {
throw new InvalidArgumentException(
(string) message(
'Parameter `%name%` is not optional',
name: $key
)
);
}
$parameter = $new->get($key);
$new->remove($key);
$new->addProperty('requiredKeys', $key, $parameter);
}
return $new;
}
public function without(string ...$name): ParametersInterface
{
$new = clone $this;
$new->remove(...$name);
return $new;
}
public function withMerge(ParametersInterface $parameters): ParametersInterface
{
$new = clone $this;
foreach ($parameters as $name => $parameter) {
$container = match ($parameters->requiredKeys()->contains($name)) {
true => 'requiredKeys',
default => 'optionalKeys',
};
$new->addProperty($container, $name, $parameter);
}
return $new;
}
public function withOptionalMinimum(int $count): ParametersInterface
{
match (true) {
$count < 0 => throw new InvalidArgumentException(
(string) message('Count must be greater or equal to 0')
),
$this->optionalKeys()->count() === 0 => throw new BadMethodCallException(
(string) message('No optional parameters found')
),
default => null,
};
$new = clone $this;
$new->optionalMinimum = $count;
$new->assertMinimumOptional();
return $new;
}
public function requiredKeys(): VectorInterface
{
return $this->requiredKeys;
}
public function optionalKeys(): VectorInterface
{
return $this->optionalKeys;
}
public function optionalMinimum(): int
{
return $this->optionalMinimum;
}
public function assertHas(string ...$name): void
{
$this->map->assertHas(...$name);
}
public function has(string ...$name): bool
{
return $this->map->has(...$name);
}
public function get(string $name): ParameterInterface
{
return $this->map->get($name);
}
public function required(string $name): ParameterCastInterface
{
$parameter = $this->get($name);
if ($this->optionalKeys()->contains($name)) {
throw new InvalidArgumentException(
(string) message(
'Parameter `%name%` is optional',
name: $name
)
);
}
return new ParameterCast($parameter);
}
public function optional(string $name): ParameterCastInterface
{
$parameter = $this->get($name);
if (! $this->optionalKeys()->contains($name)) {
throw new InvalidArgumentException(
(string) message(
'Parameter `%name%` is required',
name: $name
)
);
}
return new ParameterCast($parameter);
}
private function remove(string ...$name): void
{
$this->map = $this->map->without(...$name);
$requiredDiff = array_diff($this->requiredKeys->toArray(), $name);
$optionalDiff = array_diff($this->optionalKeys->toArray(), $name);
$this->requiredKeys = new Vector(...$requiredDiff);
$this->optionalKeys = new Vector(...$optionalDiff);
$this->assertMinimumOptional();
}
private function assertMinimumOptional(): void
{
if ($this->optionalMinimum > $this->optionalKeys()->count()) {
throw new InvalidArgumentException(
(string) message(
'Count must be less or equal to `%optional%`',
optional: strval($this->optionalMinimum)
)
);
}
}
private function addProperty(string $property, string $name, ParameterInterface $parameter): void
{
if ($this->has($name)) {
throw new OverflowException(
(string) message(
'Parameter `%name%` has been already added',
name: $name
)
);
}
$this->{$property} = $this->{$property}->withPush($name);
$this->map = $this->map->withPut($name, $parameter);
}
}
|