From e0a40e4a79e361ed91cbbe5bf6897635f28ddec6 Mon Sep 17 00:00:00 2001 From: Joachim Noreiko Date: Mon, 16 Sep 2024 18:38:16 +0100 Subject: [PATCH] Added sorting of options and weight property. Fixes #18. --- Definition/DataDefinition.php | 55 ++++++++++++++++++- Definition/OptionDefinition.php | 24 +++++++- Definition/OptionsSortOrder.php | 23 ++++++++ README.md | 7 ++- .../SerializationTestDefinition.php | 7 +++ Test/src/DataDefinitionTest.php | 51 +++++++++++++---- 6 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 Definition/OptionsSortOrder.php diff --git a/Definition/DataDefinition.php b/Definition/DataDefinition.php index ad86147..6879cac 100644 --- a/Definition/DataDefinition.php +++ b/Definition/DataDefinition.php @@ -92,6 +92,8 @@ class DataDefinition implements PropertyListInterface { protected $options = NULL; + protected ?OptionsSortOrder $optionsOrder = NULL; + protected $validators = []; /** @@ -698,6 +700,11 @@ public function setOptionsArray(array $options_array): self { return $this; } + public function setOptionsSorting(OptionsSortOrder $order): self { + $this->optionsOrder = $order; + return $this; + } + public function hasOptions(): bool { return !empty($this->options) || !empty($this->optionSet); } @@ -708,19 +715,61 @@ public function hasOptions(): bool { * These can be either the options set directly on this definition, or * obtained dynamically from an option set definition. * + * The options are returned sorted by option weight, and then the sort order + * set on this definition. If not specified, this defaults to the order in + * which the options were added to the definition. + * * @return \MutableTypedData\Definition\OptionDefinition[] * An array of option definitions, keyed by the option values. */ public function getOptions(): array { if ($this->optionSet) { - return $this->optionSet->getOptions(); + $options = $this->optionSet->getOptions(); } elseif ($this->options) { - return $this->options; + $options = $this->options; } else { - return []; + $options = []; + } + + $options_sorting = $this->getOptionsSorting() ?? OptionsSortOrder::Original; + + if ($options_sorting == OptionsSortOrder::Original) { + // Get the order in which the items were added to the options array in the + // definition (or in which they're returned from the option set definition). + // This is an array of all option values, keyed by the option value, whose + // values are increasing integers. + $added_order = array_flip(array_keys($options)); + + uasort($options, function ($a, $b) use ($added_order) { + // Options with the same weight are sorted by the order they were added + // to the options array. + if ($a->getWeight() == $b->getWeight()) { + return $added_order[$a->getValue()] <=> $added_order[$b->getValue()]; + } + else { + return $a->getWeight() <=> $b->getWeight(); + } + }); } + elseif ($options_sorting == OptionsSortOrder::Label) { + uasort($options, function ($a, $b) { + // Options with the same weight are sorted by label. + if ($a->getWeight() == $b->getWeight()) { + return strcmp($a->getLabel(), $b->getLabel()); + } + else { + return $a->getWeight() <=> $b->getWeight(); + } + }); + } + + return $options; + } + + public function getOptionsSorting(): ?OptionsSortOrder { + return $this->optionsOrder; } public function setValidators(string ...$validators): self { diff --git a/Definition/OptionDefinition.php b/Definition/OptionDefinition.php index 5171feb..b9fe24e 100644 --- a/Definition/OptionDefinition.php +++ b/Definition/OptionDefinition.php @@ -28,12 +28,22 @@ class OptionDefinition { */ protected $description = ''; - public function __construct($value, $label, $description = NULL) { + /** + * An optional weight for sorting the option. + * + * @var int + */ + protected $weight = 0; + + public function __construct($value, $label, $description = NULL, int $weight = 0) { $this->value = $value; $this->label = $label; if ($description) { $this->description = $description; } + if ($weight) { + $this->weight = $weight; + } } /** @@ -45,11 +55,15 @@ public function __construct($value, $label, $description = NULL) { * The label that is shown by UIs to the user. * @param string $description * (optional) Additional text to show to the user in UIs. + * @param int $weight + * (optional) The weight for sorting the option in the list. Larger numbers + * are heavier and sink to the bottom. Options with identical weights are + * shown in the sort order defined by the data definition. * * @return static */ - public static function create($value, string $label, string $description = NULL): self { - return new static($value, $label, $description); + public static function create($value, string $label, string $description = NULL, int $weight = 0): self { + return new static($value, $label, $description, $weight); } public function getValue() { @@ -64,4 +78,8 @@ public function getDescription() { return $this->description; } + public function getWeight(): int { + return $this->weight; + } + } diff --git a/Definition/OptionsSortOrder.php b/Definition/OptionsSortOrder.php new file mode 100644 index 0000000..6a8710c --- /dev/null +++ b/Definition/OptionsSortOrder.php @@ -0,0 +1,23 @@ +setOptions( \MutableTypedData\Definition\OptionDefinition::create('green', 'Emerald', 'A lovely shade of green'), \MutableTypedData\Definition\OptionDefinition::create('red', 'Magenta', 'A deep red'), - \MutableTypedData\Definition\OptionDefinition::create('grey', 'Grey', 'Not very colourful') + \MutableTypedData\Definition\OptionDefinition::create('grey', 'Grey', 'Not very colourful but shows at the top', -10) ); ``` +Higher-valued weights 'sink' to the bottom of a list of options; lower-valued +weights are lighter and 'rise' up. + ### Mutable data Mutable data needs a single property to control the variants, and then a diff --git a/Test/fixtures/Definition/SerializationTestDefinition.php b/Test/fixtures/Definition/SerializationTestDefinition.php index 604a50c..3dcf65a 100644 --- a/Test/fixtures/Definition/SerializationTestDefinition.php +++ b/Test/fixtures/Definition/SerializationTestDefinition.php @@ -6,6 +6,7 @@ use MutableTypedData\Definition\DefaultDefinition; use MutableTypedData\Definition\DefinitionProviderInterface; use MutableTypedData\Definition\OptionDefinition; +use MutableTypedData\Definition\OptionsSortOrder; use MutableTypedData\Definition\DataDefinition; use MutableTypedData\Definition\VariantDefinition; @@ -15,6 +16,12 @@ public static function getDefinition(): DataDefinition { $definition = DataDefinition::create('complex') ->setProperties([ 'one' => DataDefinition::create('string'), + 'string_with_options' => DataDefinition::create('string') + ->setOptionsArray([ + 'option_one' => 'One', + 'option_two' => 'Two', + ]) + ->setOptionsSorting(OptionsSortOrder::Label), 'two' => DataDefinition::create('complex') ->setProperties([ 'alpha' => DataDefinition::create('string'), diff --git a/Test/src/DataDefinitionTest.php b/Test/src/DataDefinitionTest.php index 6023641..17b022e 100644 --- a/Test/src/DataDefinitionTest.php +++ b/Test/src/DataDefinitionTest.php @@ -4,6 +4,7 @@ use MutableTypedData\DataItemFactory; use MutableTypedData\Definition\DataDefinition; +use MutableTypedData\Definition\OptionsSortOrder; use PHPUnit\Framework\TestCase; /** @@ -84,16 +85,16 @@ public function testExceptions(callable $call) { */ public function testDefineChildProperties() { $definition = DataDefinition::create('complex') - ->setLabel('Label') - ->setRequired(TRUE) - ->setProperties([ - 'alpha' => DataDefinition::create('string') - ->setLabel('Label') - ->setRequired(TRUE), - 'beta' => DataDefinition::create('string') - ->setLabel('Label') - ->setRequired(TRUE), - ]); + ->setLabel('Label') + ->setRequired(TRUE) + ->setProperties([ + 'alpha' => DataDefinition::create('string') + ->setLabel('Label') + ->setRequired(TRUE), + 'beta' => DataDefinition::create('string') + ->setLabel('Label') + ->setRequired(TRUE), + ]); $this->assertEquals(['alpha', 'beta'], array_keys($definition->getProperties())); @@ -112,4 +113,34 @@ public function testDefineChildProperties() { ); } + /** + * Test sorting of options. + */ + public function testOptionsOrder() { + $definition = DataDefinition::create('string') + ->setOptions( + \MutableTypedData\Definition\OptionDefinition::create('dull', 'Dull', weight: 10), + \MutableTypedData\Definition\OptionDefinition::create('normal_zulu', 'Normal Zulu'), + \MutableTypedData\Definition\OptionDefinition::create('normal_alpha', 'Normal Alpha'), + \MutableTypedData\Definition\OptionDefinition::create('important', 'Important', weight: -10), + ); + + $data = DataItemFactory::createFromDefinition($definition); + $this->assertEquals([ + 'important', + 'normal_zulu', + 'normal_alpha', + 'dull', + ], array_keys($data->getOptions())); + + // Change the options to be sorted by label. + $definition->setOptionsSorting(OptionsSortOrder::Label); + $this->assertEquals([ + 'important', + 'normal_alpha', + 'normal_zulu', + 'dull', + ], array_keys($data->getOptions())); + } + }