Skip to content

Commit

Permalink
Added sorting of options and weight property. Fixes #18.
Browse files Browse the repository at this point in the history
  • Loading branch information
joachim-n committed Sep 17, 2024
1 parent 76d43e7 commit e0a40e4
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 18 deletions.
55 changes: 52 additions & 3 deletions Definition/DataDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ class DataDefinition implements PropertyListInterface {

protected $options = NULL;

protected ?OptionsSortOrder $optionsOrder = NULL;

protected $validators = [];

/**
Expand Down Expand Up @@ -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);
}
Expand All @@ -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 {
Expand Down
24 changes: 21 additions & 3 deletions Definition/OptionDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

/**
Expand All @@ -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() {
Expand All @@ -64,4 +78,8 @@ public function getDescription() {
return $this->description;
}

public function getWeight(): int {
return $this->weight;
}

}
23 changes: 23 additions & 0 deletions Definition/OptionsSortOrder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace MutableTypedData\Definition;

/**
* Defines the possible values for options sort order.
*
* Option weight always overrides this order. In other words, the order defined
* with this value only applies to options of the same weight.
*/
enum OptionsSortOrder {

/**
* Options are sorted in the order in which they were added to the definition.
*/
case Original;

/**
* Options are sorted by their label.
*/
case Label;

}
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ $definition = \MutableTypedData\Definition\DataDefinition::create('complex')
### Options

Options can be defined with an array, or with objects, which allow options to
have descriptions as well as labels:
have descriptions and weights as well as labels:

```
$definition = \MutableTypedData\Definition\DataDefinition::create('string')
Expand All @@ -243,10 +243,13 @@ $definition = \MutableTypedData\Definition\DataDefinition::create('string')
->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
Expand Down
7 changes: 7 additions & 0 deletions Test/fixtures/Definition/SerializationTestDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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'),
Expand Down
51 changes: 41 additions & 10 deletions Test/src/DataDefinitionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use MutableTypedData\DataItemFactory;
use MutableTypedData\Definition\DataDefinition;
use MutableTypedData\Definition\OptionsSortOrder;
use PHPUnit\Framework\TestCase;

/**
Expand Down Expand Up @@ -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()));

Expand All @@ -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()));
}

}

0 comments on commit e0a40e4

Please sign in to comment.