<?php
namespace App\Model\Query\ConditionQuery;
use App\Model\Query\ConditionQuery\Condition\ContainsCondition;
use App\Model\Query\ConditionQuery\Condition\EmptyCondition;
use App\Model\Query\ConditionQuery\Condition\NotContainsCondition;
use App\Model\Query\ConditionQuery\Condition\NotEmptyCondition;
use App\Model\Query\ConditionQuery\Condition\NotOverlapsCondition;
use App\Model\Query\ConditionQuery\Condition\OverlapsCondition;
use App\Model\Query\ConditionQuery\Type;
use JsonSerializable;
use Serializable;
/**
*
*/
abstract class AbstractType implements Serializable, JsonSerializable
{
const NAME = null;
const CLASSES = [
Type\BooleanType::NAME => Type\BooleanType::class,
Type\FloatType::NAME => Type\FloatType::class,
Type\IntegerType::NAME => Type\IntegerType::class,
Type\NumericType::NAME => Type\NumericType::class,
Type\StringType::NAME => Type\StringType::class,
Type\RoleType::NAME => Type\RoleType::class,
Type\RoleTypeType::NAME => Type\RoleTypeType::class,
Type\GradeType::NAME => Type\GradeType::class,
Type\SchoolType::NAME => Type\SchoolType::class,
Type\DateType::NAME => Type\DateType::class,
];
const VALID_ARRAY_OPERATOR_NAMES = [
OverlapsCondition::NAME => OverlapsCondition::HUMAN_READABLE_NAME,
ContainsCondition::NAME => ContainsCondition::HUMAN_READABLE_NAME,
];
/**
* @var AbstractOperatorCondition|null
*/
protected ?AbstractOperatorCondition $condition = null;
/**
* Whether the defined type allows for either a single value or multiple values.
*
* @var bool
*/
protected bool $multiple = false;
/**
* If this type is an enumerated type, this should be the name of the "dictionary" to use.
*
* @var string|null
*/
protected ?string $enum = null;
/**
* @param bool $multiple
* @param string|null $enum
*/
public function __construct(
bool $multiple = false,
?string $enum = null
)
{
$this->multiple = $multiple;
$this->enum = $enum ?: null;
}
/**
* @return AbstractOperatorCondition|null
*/
public function getCondition(): ?AbstractOperatorCondition
{
return $this->condition;
}
/**
* @return ConditionConfig|null
*/
public function getConditionConfig(): ?ConditionConfig
{
if ($this->getCondition()) {
return $this->getCondition()->getConditionConfig();
}
return null;
}
/**
* @return ConditionSchema|null
*/
public function getConditionSchema(): ?ConditionSchema
{
if ($this->getCondition()) {
return $this->getCondition()->getConditionSchema();
}
return null;
}
/**
* @return ConditionQuery|null
*/
public function getConditionQuery(): ?ConditionQuery
{
if ($this->getCondition()) {
return $this->getCondition()->getConditionQuery();
}
return null;
}
/**
* @return bool
*/
public function allowsMultiple(): bool
{
return $this->multiple;
}
/**
* @return string|null
*/
public function getEnum(): ?string
{
return $this->enum;
}
/**
* @return bool
*/
public function isEnum(): bool
{
return (bool) $this->getEnum();
}
/**
* @return ConditionDictionary|null
*/
public function getConditionDictionary(): ?ConditionDictionary
{
if ($this->isEnum() && ($config = $this->getConditionConfig())) {
if ( ! $config->hasDictionary($this->getEnum())) {
throw new \Exception();
}
return $config->getDictionary($this->getEnum());
}
return null;
}
/**
* Takes a potential value (what would be set on a query condition) and determines if it is legal for this type.
* Note the helper method for "validateMultiples" in this class.
* Validating the value also involves validating whether the type allows for multiple values or allows only single values.
*
* @param mixed $value
* @return void
*/
abstract public function validate($value): void;
/**
* @param mixed $value
* @return void
*/
protected function validateMultiples($value): void
{
if (is_array($value) && ! $this->allowsMultiple()) {
throw new \Exception(sprintf(
'Type "%s" does not support multiples.',
$this->getName(),
));
}
}
/**
* @param array<string>|string $serialized
* @return AbstractType
*/
static public function factory($serialized): AbstractType
{
// determine the name
$name = is_array($serialized) ? $serialized['name'] : $serialized;
// strip potential brackets to get the root type
$type = preg_replace('/[^_a-z]/', '', $name);
// make sure we support this type
if ( ! array_key_exists($type, self::CLASSES)) {
throw new \LogicException();
}
// get the class name for the specific type
$class = self::CLASSES[$type];
// make an instance of ot
$object = new $class();
// just double check that we have a proper object
if ( ! $object instanceof AbstractType) {
throw new \LogicException();
}
// finish setting up the initial object by unserializing the original input
// this should handle setting multiples or not
$object->jsonUnserialize($serialized);
return $object;
}
/**
* @return string|null
*/
public function getName(): ?string
{
return static::NAME;
}
/**
* {@inheritDoc}
*/
public function serialize(): string
{
// returns pattern of "type" for no multiples or "type[]" for multiples
return $this->getName() . ($this->allowsMultiple() ? '[]' : '');
}
/**
* {@inheritDoc}
*/
public function unserialize($serialized): void
{
switch ($serialized) {
// if just the name of the type, doesn't allow multiples
case static::NAME:
$this->multiple = false;
break;
// if the name ends with [], then it allows multiples
case static::NAME . '[]':
$this->multiple = true;
break;
// if we didn't catch on anything yet, there's likely a problem with the code...
default:
throw new \LogicException();
}
}
/**
* {@inheritDoc}
* @return array<string>|string
*/
public function jsonSerialize()
{
if ( ! $this->isEnum()) {
return $this->serialize();
}
return [
'name' => $this->serialize(),
'enum' => $this->getEnum(),
];
}
/**
* @param array<string>|string $data
* @return $this
*/
public function jsonUnserialize($data): self
{
if (is_string($data)) {
$this->unserialize($data);
} else {
$this->unserialize($data['name']);
$this->enum = $data['enum'] ?? null;
}
return $this;
}
/**
* @return string[]
*/
abstract public function getValidOperators(): array;
}