Skip to content

Commit 70645a3

Browse files
author
Dominic Tubach
committed
Fix states array creation
Previously, checkboxes weren't treated correctly and rules where not possible in arrays.
1 parent 27b175b commit 70645a3

22 files changed

+775
-143
lines changed

phpstan.neon.dist

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,5 @@ parameters:
3030
paths:
3131
- */src/JsonForms/Definition/Control/ControlDefinition.php
3232
- */src/JsonForms/Definition/Layout/LayoutDefinition.php
33-
-
34-
message: '#^Argument of an invalid type stdClass supplied for foreach, only iterables are supported.$#'
35-
path: */src/Form/Control/ObjectArrayFactory.php
3633
- '#^Method Drupal\\json_forms\\JsonForms\\Definition\\Control\\[^\s]+ControlDefinition::[^\s]+\(\) should return [^\s]+\|null but returns mixed.$#'
3734
tmpDir: .phpstan

src/Form/Control/ArrayArrayFactory.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Drupal\json_forms\JsonForms\Definition\Control\ArrayControlDefinition;
3333
use Drupal\json_forms\JsonForms\Definition\Control\ControlDefinition;
3434
use Drupal\json_forms\JsonForms\Definition\DefinitionInterface;
35+
use Drupal\json_forms\JsonForms\Definition\Layout\ArrayLayoutDefinition;
3536
use Drupal\json_forms\JsonForms\Definition\Layout\LayoutDefinition;
3637

3738
final class ArrayArrayFactory extends AbstractConcreteFormArrayFactory {
@@ -185,7 +186,7 @@ private function buildTableHeader(LayoutDefinition $arrayLayoutDefinition): arra
185186
return $header;
186187
}
187188

188-
private function createLayoutDefinition(ArrayControlDefinition $definition): LayoutDefinition {
189+
private function createLayoutDefinition(ArrayControlDefinition $definition): ArrayLayoutDefinition {
189190
// Note: We actually do not use "detail" as it is described in JSON Forms:
190191
// https://jsonforms.io/docs/uischema/controls#the-detail-option
191192
// Should we use another option name instead?
@@ -200,7 +201,9 @@ private function createLayoutDefinition(ArrayControlDefinition $definition): Lay
200201
Assertion::isInstanceOf($items, \stdClass::class);
201202
$arrayUiSchema->elements ??= $this->createElementSchemas($items);
202203

203-
return new LayoutDefinition($arrayUiSchema, $items, $definition->isUiReadonly());
204+
return new ArrayLayoutDefinition(
205+
$arrayUiSchema, $items, $definition->isUiReadonly(), $definition->getRootDefinition()
206+
);
204207
}
205208

206209
/**

src/Form/Control/ObjectArrayFactory.php

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@
2525
use Drupal\Core\Form\FormStateInterface;
2626
use Drupal\json_forms\Form\AbstractConcreteFormArrayFactory;
2727
use Drupal\json_forms\Form\FormArrayFactoryInterface;
28-
use Drupal\json_forms\JsonForms\Definition\Control\ControlDefinition;
2928
use Drupal\json_forms\JsonForms\Definition\Control\ObjectControlDefinition;
3029
use Drupal\json_forms\JsonForms\Definition\DefinitionInterface;
31-
use Drupal\json_forms\JsonForms\Definition\Layout\LayoutDefinition;
3230

3331
final class ObjectArrayFactory extends AbstractConcreteFormArrayFactory {
3432

@@ -40,39 +38,13 @@ public function createFormArray(
4038
FormStateInterface $formState,
4139
FormArrayFactoryInterface $formArrayFactory
4240
): array {
43-
Assertion::isInstanceOf($definition, ControlDefinition::class);
44-
/** @var \Drupal\json_forms\JsonForms\Definition\Control\ControlDefinition $definition */
45-
$definition = ObjectControlDefinition::fromDefinition($definition);
46-
/** @var \Drupal\json_forms\JsonForms\Definition\Control\ObjectControlDefinition $definition */
41+
Assertion::isInstanceOf($definition, ObjectControlDefinition::class);
4742

48-
$layoutSchema = $this->createLayoutSchema($definition);
49-
$layoutDefinition = new LayoutDefinition(
50-
$layoutSchema,
51-
$definition->getPropertySchema(),
52-
$definition->isUiReadonly()
53-
);
54-
55-
return $formArrayFactory->createFormArray($layoutDefinition, $formState);
43+
return $formArrayFactory->createFormArray($definition->getLayoutDefinition(), $formState);
5644
}
5745

5846
public function supportsDefinition(DefinitionInterface $definition): bool {
59-
return $definition instanceof ControlDefinition && 'object' === $definition->getType();
60-
}
61-
62-
private function createLayoutSchema(ObjectControlDefinition $definition): \stdClass {
63-
$layoutSchema = (object) [
64-
'type' => 'VerticalLayout',
65-
'elements' => [],
66-
];
67-
68-
foreach ($definition->getProperties() as $propertyName => $propertySchema) {
69-
$layoutSchema->elements[] = (object) [
70-
'type' => 'Control',
71-
'scope' => '#/properties/' . $propertyName,
72-
];
73-
}
74-
75-
return $layoutSchema;
47+
return $definition instanceof ObjectControlDefinition;
7648
}
7749

7850
}

src/Form/Control/Rule/StatesArrayFactory.php

Lines changed: 61 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,22 @@
2121

2222
namespace Drupal\json_forms\Form\Control\Rule;
2323

24-
use Drupal\json_forms\Form\Control\Util\FormPropertyUtil;
25-
use Drupal\json_forms\JsonForms\ScopePointer;
26-
24+
use Drupal\json_forms\JsonForms\Definition\Control\ObjectControlDefinition;
25+
use Drupal\json_forms\JsonForms\Definition\DefinitionInterface;
26+
use Drupal\json_forms\JsonForms\Definition\Layout\LayoutDefinition;
27+
28+
/**
29+
* This class tries to create a Drupal states array from a JSON Forms rule.
30+
* Though, not everything in a condition schema can be mapped to Drupal
31+
* functionality.
32+
*
33+
* Note: In JSON Forms controls inside of arrays cannot reference properties
34+
* outside of that array.
35+
* See: https://github.com/eclipsesource/jsonforms/issues/2094.
36+
*
37+
* @see https://jsonforms.io/docs/uischema/rules
38+
* @see https://www.drupal.org/docs/drupal-apis/form-api/conditional-form-fields
39+
*/
2740
final class StatesArrayFactory implements StatesArrayFactoryInterface {
2841

2942
private StatesBuilder $statesBuilder;
@@ -35,49 +48,72 @@ public function __construct() {
3548
/**
3649
* @phpstan-return array<string, mixed>
3750
*/
38-
public function createStatesArray(\stdClass $rule): array {
51+
public function createStatesArray(DefinitionInterface $definition): array {
52+
$rule = $definition->getRule();
53+
if (NULL === $rule) {
54+
return [];
55+
}
56+
57+
$rootDefinition = $definition->getRootDefinition();
58+
if ($rootDefinition instanceof ObjectControlDefinition) {
59+
$rootDefinition = $rootDefinition->getLayoutDefinition();
60+
}
61+
elseif (!$rootDefinition instanceof LayoutDefinition) {
62+
return [];
63+
}
64+
3965
$this->statesBuilder->clear();
4066
$this->addStates(
4167
$rule->effect,
42-
$this->getFieldName($rule->condition->scope),
43-
$rule->condition->schema,
68+
$rootDefinition,
69+
$rule->condition->scope,
70+
$rule->condition->schema
4471
);
4572

4673
return $this->statesBuilder->toArray();
4774
}
4875

49-
private function getFieldName(string $scope): string {
50-
return FormPropertyUtil::getFormNameForPropertyPath(ScopePointer::new($scope)->getPropertyPath());
51-
}
52-
5376
private function addStates(
5477
string $effect,
55-
string $fieldName,
56-
\stdClass $schema,
78+
LayoutDefinition $rootDefinition,
79+
string $scope,
80+
\stdClass $conditionSchema,
5781
bool $negate = FALSE,
5882
bool $isContains = FALSE
5983
): void {
60-
if (property_exists($schema, 'not')) {
61-
$this->addStates($effect, $fieldName, $schema->not, !$negate, $isContains);
84+
if (property_exists($conditionSchema, 'const')) {
85+
$controlDefinition = $rootDefinition->findControlDefinition($scope);
86+
if (NULL !== $controlDefinition) {
87+
$this->statesBuilder->add($effect, $controlDefinition, $conditionSchema->const, $negate, $isContains);
88+
}
6289
}
6390

64-
if (property_exists($schema, 'const')) {
65-
$this->statesBuilder->add($effect, $fieldName, $schema->const, $negate, $isContains);
91+
if (property_exists($conditionSchema, 'enum')) {
92+
$controlDefinition = $rootDefinition->findControlDefinition($scope);
93+
if (NULL !== $controlDefinition) {
94+
$this->statesBuilder->add($effect, $controlDefinition, $conditionSchema->enum, $negate, $isContains);
95+
}
6696
}
6797

68-
if (property_exists($schema, 'enum')) {
69-
$this->statesBuilder->add($effect, $fieldName, $schema->enum, $negate, $isContains);
98+
if (property_exists($conditionSchema, 'not')) {
99+
$this->addStates($effect, $rootDefinition, $scope, $conditionSchema->not, !$negate, $isContains);
70100
}
71101

72-
if (property_exists($schema, 'properties')) {
73-
foreach ($schema->properties as $property => $propertySchema) {
74-
$propertyFieldName = $fieldName . '[' . $property . ']';
75-
$this->addStates($effect, $propertyFieldName, $propertySchema, $negate, $isContains);
76-
}
102+
if (property_exists($conditionSchema, 'contains')) {
103+
$this->addStates($effect, $rootDefinition, $scope, $conditionSchema->contains, $negate, TRUE);
77104
}
78105

79-
if (property_exists($schema, 'contains')) {
80-
$this->addStates($effect, $fieldName, $schema->contains, $negate, TRUE);
106+
if (property_exists($conditionSchema, 'properties')) {
107+
foreach ($conditionSchema->properties as $property => $propertyConditionSchema) {
108+
$this->addStates(
109+
$effect,
110+
$rootDefinition,
111+
"$scope/properties/$property",
112+
$propertyConditionSchema,
113+
$negate,
114+
$isContains
115+
);
116+
}
81117
}
82118
}
83119

src/Form/Control/Rule/StatesArrayFactoryInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121

2222
namespace Drupal\json_forms\Form\Control\Rule;
2323

24+
use Drupal\json_forms\JsonForms\Definition\DefinitionInterface;
25+
2426
interface StatesArrayFactoryInterface {
2527

2628
/**
2729
* @phpstan-return array<string, mixed>
2830
*/
29-
public function createStatesArray(\stdClass $rule): array;
31+
public function createStatesArray(DefinitionInterface $definition): array;
3032

3133
}

src/Form/Control/Rule/StatesBuilder.php

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121

2222
namespace Drupal\json_forms\Form\Control\Rule;
2323

24+
use Drupal\json_forms\Form\Control\Util\FormPropertyUtil;
25+
use Drupal\json_forms\JsonForms\Definition\Control\ControlDefinition;
26+
use Drupal\json_forms\JsonForms\ScopePointer;
27+
2428
/**
2529
* @phpstan-type statesT array<string, array<string|int, array<array<string, mixed>|'and'|'or'|'xor'>>>
2630
* @phpstan-type conditionT array{checked: bool}|array{empty: bool}|array{value: scalar}
@@ -37,11 +41,12 @@ final class StatesBuilder {
3741
*/
3842
public function add(
3943
string $effect,
40-
string $fieldName,
44+
ControlDefinition $definition,
4145
$value,
4246
bool $negate,
4347
bool $isContains
4448
): self {
49+
$fieldName = $this->getFieldName($definition->getFullScope());
4550
foreach ($this->getStates($effect, $negate) as $state) {
4651
if ($isContains) {
4752
$this->states[$state][] = $this->buildContainsCondition($fieldName, $value);
@@ -52,7 +57,7 @@ public function add(
5257
$this->states[$state][$selector][] = 'and';
5358
}
5459

55-
$this->states[$state][$selector][] = $this->buildCondition($value);
60+
$this->states[$state][$selector][] = $this->buildCondition($definition, $value);
5661
}
5762
}
5863

@@ -63,6 +68,13 @@ public function clear(): void {
6368
$this->states = [];
6469
}
6570

71+
/**
72+
* @phpstan-return statesT
73+
*/
74+
public function toArray(): array {
75+
return $this->states;
76+
}
77+
6678
/**
6779
* @phpstan-return array<string>
6880
*
@@ -88,40 +100,51 @@ private function getStates(string $effect, bool $negate): array {
88100
}
89101
}
90102

103+
private function getFieldName(string $scope): string {
104+
return '#' === $scope ? ''
105+
: FormPropertyUtil::getFormNameForPropertyPath(ScopePointer::new($scope)->getPropertyPath());
106+
}
107+
91108
/**
92109
* @phpstan-param scalar|array<scalar>|null $value
93110
*
94111
* @phpstan-return conditionT|array<conditionT|'or'>
95112
*/
96-
private function buildCondition($value): array {
97-
if (is_bool($value)) {
98-
return [
99-
['checked' => $value],
100-
'and',
101-
['value' => $value ? '1' : '0'],
102-
];
103-
}
113+
private function buildCondition(ControlDefinition $definition, $value): array {
114+
if (is_array($value)) {
115+
$condition = [];
116+
foreach ($value as $v) {
117+
if (!is_array($v)) {
118+
/** @phpstan-var conditionT $subCondition */
119+
$subCondition = $this->buildCondition($definition, $v);
120+
$condition[] = $subCondition;
121+
$condition[] = 'or';
122+
}
123+
}
124+
array_pop($condition);
104125

105-
if (NULL === $value) {
106-
return ['empty' => TRUE];
126+
return $condition;
107127
}
108128

109-
if (!is_array($value)) {
110-
return ['value' => (string) $value];
129+
if (is_bool($value)) {
130+
if ('boolean' === $definition->getType() && 'radio' !== $definition->getControlFormat()) {
131+
// checkbox.
132+
return ['checked' => $value];
133+
}
134+
135+
return ['value' => $value ? '1' : '0'];
111136
}
112137

113-
$condition = [];
114-
foreach ($value as $v) {
115-
if (!is_array($v)) {
116-
/** @phpstan-var conditionT $subCondition */
117-
$subCondition = $this->buildCondition($v);
118-
$condition[] = $subCondition;
119-
$condition[] = 'or';
120-
}
138+
$value = (string) $value;
139+
if ('' === $value) {
140+
return [
141+
['empty' => TRUE],
142+
'or',
143+
['value' => $value],
144+
];
121145
}
122-
array_pop($condition);
123146

124-
return $condition;
147+
return ['value' => $value];
125148
}
126149

127150
/**
@@ -152,11 +175,4 @@ private function buildContainsCondition(string $fieldName, $value): array {
152175
return $condition;
153176
}
154177

155-
/**
156-
* @phpstan-return statesT
157-
*/
158-
public function toArray(): array {
159-
return $this->states;
160-
}
161-
162178
}

src/Form/Control/Util/BasicFormPropertiesFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static function createBasicProperties(ControlDefinition $definition): arr
5858

5959
if (NULL !== $definition->getRule()) {
6060
$statesArrayFactory = new StatesArrayFactory();
61-
$form['#states'] = $statesArrayFactory->createStatesArray($definition->getRule());
61+
$form['#states'] = $statesArrayFactory->createStatesArray($definition);
6262
}
6363

6464
// Custom option to hide labels, so they are not shown in the form by

src/Form/Layout/AbstractLayoutArrayFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function createFormArray(
5555
DescriptionDisplayUtil::handleDescriptionDisplay($form, $definition->getOptionsValue('descriptionDisplay'));
5656

5757
if (NULL !== $definition->getRule()) {
58-
$form['#states'] = $this->statesArrayFactory->createStatesArray($definition->getRule());
58+
$form['#states'] = $this->statesArrayFactory->createStatesArray($definition);
5959
}
6060

6161
return array_merge($form, $this->createElementsFormArray($definition, $formState, $formArrayFactory));

src/Form/Markup/HtmlMarkupArrayFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function createFormArray(
5656
];
5757

5858
if (NULL !== $definition->getRule()) {
59-
$element['#states'] = $this->statesArrayFactory->createStatesArray($definition->getRule());
59+
$element['#states'] = $this->statesArrayFactory->createStatesArray($definition);
6060
}
6161

6262
return $element;

0 commit comments

Comments
 (0)