<?php
namespace App\Model\Query\ConditionQuery;
use App\Util\Arrays;
use JsonSerializable;
/**
* Top level object that contains all the filters that are defined for a customer.
* This could have been stored simplay as just an array with no need for this class.
* However, it is expected that there may be more properties needed in the future.
*
* @see https://coda.io/d/_diyKeRjYYS-/Flexible-Messaging-Prototype_suNzV#_luVi2
*/
final class ConditionConfig implements JsonSerializable
{
/**
* The various filtering options that are defined in this configuration.
*
* @var array<ConditionSchema>
*/
protected array $schemas = [];
/**
* Any key/value lookups that certain filters may reference in their schema.
*
* @var array<ConditionDictionary>
*/
protected array $dictionaries = [];
/**
* @var array<ConditionLookup>
*/
protected array $lookups = [];
/**
* @param array<ConditionSchema> $schemas
* @param array<ConditionDictionary> $dictionaries
* @param array<ConditionLookup> $lookups
*/
public function __construct(
array $schemas = [],
array $dictionaries = [],
array $lookups = []
)
{
$this
->setSchemas($schemas)
->setDictionaries($dictionaries)
->setLookups($lookups);
}
/**
* @param ConditionConfig $config
* @return $this
*/
public function merge(ConditionConfig $config): self
{
foreach ($config->getSchemas() as $schema) {
if ( ! $this->hasSchema($schema->getFilter())) {
$this->addSchema(clone $schema);
}
}
foreach ($config->getDictionaries() as $dictionary) {
if ( ! $this->hasDictionary($dictionary->getName())) {
$this->addDictionary(clone $dictionary);
}
}
foreach ($config->getLookups() as $lookup) {
if ( ! $this->hasLookup($lookup->getName())) {
$this->addLookup(clone $lookup);
}
}
return $this;
}
/**
* @return array<ConditionSchema>
*/
public function getSchemas(): array
{
return $this->schemas;
}
/**
* @param array<ConditionSchema> $schemas
* @return $this
*/
public function setSchemas(array $schemas): self
{
$this->schemas = [];
foreach ($schemas as $schema) {
$this->addSchema($schema, false);
}
$this->sortSchemas();
return $this;
}
/**
* @param string $filter
* @return bool
*/
public function hasSchema(string $filter): bool
{
return array_key_exists($filter, $this->getSchemas());
}
/**
* @param string $filter
* @return ConditionSchema|null
*/
public function getSchema(string $filter): ?ConditionSchema
{
if ( ! $this->hasSchema($filter)) {
return null;
}
return $this->getSchemas()[$filter];
}
/**
* Given a new filter, will attempt to add it to the set.
* Filters should not be added more than once.
*
* @param ConditionSchema $new
* @param bool $sort
* @return $this
*/
public function addSchema(
ConditionSchema $new,
bool $sort = true
): self
{
// do not add if we alread have the same filter; no matter if this is a new object or not
if ($this->hasSchema($new->getFilter())) {
throw new \LogicException(sprintf(
'Schema "%s" has already been added to the collection.',
$new->getFilter()
));
}
// attach the schema to the collection
$this->schemas[$new->getFilter()] = $new;
// set the parent on the schema to us
$new->setConditionConfig($this);
// sort if we are flagged to do that
if ($sort) {
$this->sortSchemas();
}
return $this;
}
/**
* Helper function to sort the schemas in the filters.
* Sorting is done by the human-friendly name.
*
* @return void
*/
protected function sortSchemas(): void
{
uasort(
$this->schemas,
static function (ConditionSchema $a, ConditionSchema $b) {
if (($result = strcasecmp($a->getCategory(), $b->getCategory())) !== 0) {
return $result;
}
return strcasecmp($a->getName(), $b->getName());
}
);
}
/**
* @return array<ConditionDictionary>
*/
public function getDictionaries(): array
{
return $this->dictionaries;
}
/**
* @param array<ConditionDictionary> $dictionaries
* @return $this
*/
public function setDictionaries(array $dictionaries): self
{
$this->dictionaries = [];
foreach ($dictionaries as $dictionary) {
$this->addDictionary($dictionary, false);
}
$this->sortDictionaries();
return $this;
}
/**
* @param string $name
* @return bool
*/
public function hasDictionary(string $name): bool
{
return array_key_exists($name, $this->getDictionaries());
}
/**
* @param string $name
* @return ConditionDictionary|null
*/
public function getDictionary(string $name): ?ConditionDictionary
{
if ( ! $this->hasDictionary($name)) {
return null;
}
return $this->getDictionaries()[$name];
}
/**
* Given a new dictionary, will attempt to add it to the set.
* Dictionaries should not be added more than once.
*
* @param ConditionDictionary $new
* @param bool $sort
* @return $this
*/
public function addDictionary(
ConditionDictionary $new,
bool $sort = true
): self
{
// see if we have already been added based on our name
if ($this->hasDictionary($new->getName())) {
throw new \LogicException(sprintf(
'Dictionary "%s" has already been added to the collection.',
$new->getName()
));
}
// add to collection
$this->dictionaries[$new->getName()] = $new;
// set its parent to us
$new->setConditionConfig($this);
// handle sorting
if ($sort) {
$this->sortDictionaries();
}
return $this;
}
/**
* Helper function to sort the schemas in the filters.
* Sorting is done by the human-friendly name.
*
* @return void
*/
protected function sortDictionaries(): void
{
uasort(
$this->dictionaries,
static function (ConditionDictionary $a, ConditionDictionary $b) {
return strcasecmp($a->getName(), $b->getName());
}
);
}
/**
* @return array<ConditionLookup>
*/
public function getLookups(): array
{
return $this->lookups;
}
/**
* @param array<ConditionLookup> $lookups
* @return $this
*/
public function setLookups(array $lookups): self
{
$this->lookups = [];
foreach ($lookups as $lookup) {
$this->addLookup($lookup, false);
}
$this->sortLookups();
return $this;
}
/**
* @param string $name
* @return bool
*/
public function hasLookup(string $name): bool
{
return array_key_exists($name, $this->getLookups());
}
/**
* @param string $name
* @return ConditionLookup|null
*/
public function getLookup(string $name): ?ConditionLookup
{
if ( ! $this->hasLookup($name)) {
return null;
}
return $this->getLookups()[$name];
}
/**
* Given a new dictionary, will attempt to add it to the set.
* Dictionaries should not be added more than once.
*
* @param ConditionLookup $new
* @param bool $sort
* @return $this
*/
public function addLookup(
ConditionLookup $new,
bool $sort = true
): self
{
// see if we have already been added based on our name
if ($this->hasLookup($new->getName())) {
throw new \LogicException(sprintf(
'Lookup "%s" has already been added to the collection.',
$new->getName()
));
}
// add to collection
$this->lookups[$new->getName()] = $new;
// set its parent to us
$new->setConditionConfig($this);
// handle sorting
if ($sort) {
$this->sortLookups();
}
return $this;
}
/**
* @return void
*/
protected function sortLookups(): void
{
uasort(
$this->lookups,
static function (ConditionLookup $a, ConditionLookup $b) {
return strcasecmp($a->getName(), $b->getName());
}
);
}
/**
* {@inheritDoc}
*/
public function jsonSerialize(): array
{
return [
'schemas' => array_map(
static function (array $schema) {
unset($schema['filter']);
return $schema;
},
Arrays::mapObjectMethods($this->getSchemas(), 'jsonSerialize'),
),
'dictionaries' => array_map(
static function (array $dictionary) {
return $dictionary['glossary'];
},
Arrays::mapObjectMethods($this->getDictionaries(), 'jsonSerialize'),
),
'lookups' => array_map(
static function (array $lookup) {
return $lookup['filters'];
},
Arrays::mapObjectMethods($this->getLookups(), 'jsonSerialize'),
),
];
}
/**
* @param array $serialized
* @return $this
*/
public function jsonUnserialize(array $serialized): self
{
return $this
->setSchemas(
array_map(
static function ($index) use ($serialized) {
// TODO: done this way for legacy config that uses numerical indexed vs filter as key
switch (true) {
// old way
case is_int($index):
$schema = $serialized['schemas'][$index];
break;
// new way
case is_string($index):
$schema = array_merge(
$serialized['schemas'][$index],
[
'filter' => $index,
],
);
break;
default:
throw new \LogicException();
}
return ConditionSchema::factory($schema);
},
array_keys($serialized['schemas'] ?? []),
),
)
->setDictionaries(
array_map(
static function (string $name) use ($serialized) {
return ConditionDictionary::factory([
'name' => $name,
'glossary' => $serialized['dictionaries'][$name],
]);
},
array_keys($serialized['dictionaries'] ?? []),
),
)
->setLookups(
array_map(
static function (string $name) use ($serialized) {
return ConditionLookup::factory([
'name' => $name,
'filters' => $serialized['lookups'][$name],
]);
},
array_keys($serialized['lookups'] ?? []),
),
);
}
/**
* @param array $serialized
* @return ConditionConfig
*/
public static function factory(array $serialized): ConditionConfig
{
return (new ConditionConfig())->jsonUnserialize($serialized);
}
}