Skip to content

Commit 78848cc

Browse files
committed
Introduce constructor & property default values with ScalarDefaultProvider
- Add ScalarDefaultProvider that determines type-appropriate defaults for properties (string='', int=0, bool=false, float=0.0, array=[], nullable=null) - Add withDefaultValues() option to PropertyAssembler (enabled by default), replacing the separate PropertyDefaultsAssembler - Add withDefaultValues() option to ConstructorAssembler (enabled by default), with automatic reordering of parameters so those with defaults come last - Add withOptionalValue() option to ConstructorAssembler, forcing all parameters to ?Type = null (matching PropertyAssembler behavior) - Remove PropertyDefaultsAssembler (functionality folded into PropertyAssembler)
1 parent 6bfdcc7 commit 78848cc

12 files changed

Lines changed: 726 additions & 205 deletions

File tree

UPGRADING.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,22 @@ If you want to have this mapping in your configuration, you can start out fresh
131131
./vendor/bin/soap-client generate:config --config=config/soap-client.php
132132
```
133133

134+
## Constructor and property default values
135+
136+
The `ConstructorAssembler` now applies type-appropriate default values to constructor parameters by default. Scalar parameters get their zero-value (`''`, `0`, `false`, `0.0`, `[]`), nullable parameters get `= null`, and parameters with defaults are reordered to the end (PHP requirement). Non-nullable complex type parameters remain without defaults. To restore the previous behavior, use:
137+
138+
```php
139+
new ConstructorAssembler((new ConstructorAssemblerOptions())->withDefaultValues(false))
140+
```
141+
142+
The `ConstructorAssembler` also supports a new `withOptionalValue()` option that forces all parameters to `?Type = null`, matching the `PropertyAssembler` behavior.
143+
144+
The `PropertyDefaultsAssembler` has been removed. Its functionality is now built into `PropertyAssembler` via `PropertyAssemblerOptions::create()->withDefaultValues()` (enabled by default). To disable:
145+
146+
```php
147+
new PropertyAssembler(PropertyAssemblerOptions::create()->withDefaultValues(false))
148+
```
149+
134150
## Regenerate classes
135151

136152
After upgrading, regenerate all your classes:

docs/code-generation/assemblers.md

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ to generate the code you want to add to the generated SOAP types.
1919
- [IteratorAssembler](#iteratorassembler)
2020
- [JsonSerializableAssembler](#jsonserializableassembler)
2121
- [PropertyAssembler](#propertyassembler)
22-
- [PropertyDefaultsAssembler](#propertydefaultsassembler)
2322
- [RequestAssembler](#requestassembler)
2423
- [ResultAssembler](#resultassembler)
2524
- [ResultProviderAssembler](#resultproviderassembler)
@@ -121,6 +120,44 @@ new ConstructorAssembler((new ConstructorAssemblerOptions())->withDocBlocks(fals
121120
}
122121
```
123122

123+
Default values are enabled by default. Scalar parameters get type-appropriate defaults (`''`, `0`, `false`, `0.0`, `[]`), nullable parameters get `= null`, and parameters with defaults are reordered to the end (PHP requirement). This can be disabled with `withDefaultValues(false)`.
124+
125+
Example
126+
```php
127+
new ConstructorAssembler((new ConstructorAssemblerOptions())->withTypeHints()->withDefaultValues())
128+
```
129+
130+
```php
131+
public function __construct(SomeClass $obj, string $prop1 = '', int $prop2 = 0)
132+
{
133+
$this->obj = $obj;
134+
$this->prop1 = $prop1;
135+
$this->prop2 = $prop2;
136+
}
137+
```
138+
139+
To disable default values:
140+
```php
141+
new ConstructorAssembler((new ConstructorAssemblerOptions())->withDefaultValues(false))
142+
```
143+
144+
Optional values can be enabled with `withOptionalValue()`. This forces ALL parameters to be nullable with `= null`, regardless of WSDL metadata. This is useful when you want to construct objects without providing all values upfront.
145+
146+
Example
147+
```php
148+
new ConstructorAssembler((new ConstructorAssemblerOptions())->withOptionalValue())
149+
```
150+
151+
```php
152+
public function __construct(?string $prop1 = null, ?SomeClass $prop2 = null)
153+
{
154+
$this->prop1 = $prop1;
155+
$this->prop2 = $prop2;
156+
}
157+
```
158+
159+
`withOptionalValue()` takes precedence over `withDefaultValues()`: when both are enabled, all parameters become `?Type = null`.
160+
124161
## FluentSetterAssembler
125162

126163
The `FluentSetterAssembler` will add a setter method to the generated class. The method will return the current instance to enable chaining.
@@ -306,30 +343,28 @@ Example output:
306343
/**
307344
* @var string
308345
*/
309-
private $prop1 = null;
346+
private string $prop1 = '';
310347
```
311348

312-
You can adjust the visibility of the property by injecting the visibility in the constructor.
349+
You can adjust the visibility of the property by injecting `PropertyAssemblerOptions` in the constructor.
313350

314351
```php
315-
new PropertyAssembler(PropertyGenerator::VISIBILITY_PROTECTED)
352+
new PropertyAssembler(PropertyAssemblerOptions::create()->withVisibility(PropertyGenerator::VISIBILITY_PROTECTED))
316353
```
317354

318355
Please note that the default ruleset has a visibility of private.
319356
If you want to override this, you will have to override all rules by calling `Phpro\SoapClient\CodeGenerator\Config\Config::setRuleSet`.
320357

321-
## PropertyDefaultsAssembler
322-
323-
This `PropertyDefaultsAssembler` can be used together with the default `PropertyAssembler` and can be used to determine basic default values for specific properties.
324-
It adds default values for following scalar types: `string`, `int`, `float`, `bool`, `array`, `mixed`.
358+
Default values are enabled by default via `withDefaultValues()`. Scalar properties get type-appropriate defaults (`''`, `0`, `false`, `0.0`, `[]`), nullable types get `= null`, and non-nullable complex types get no default.
325359

326-
Example output:
360+
This differs from `withOptionalValue()`:
361+
- `withOptionalValue()`: forces ALL properties to `?Type = null` regardless of WSDL metadata.
362+
- `withDefaultValues()`: gives type-appropriate defaults respecting the WSDL schema.
363+
- When both are enabled, `withOptionalValue()` takes precedence (everything becomes `?Type = null`).
327364

365+
To disable default values:
328366
```php
329-
/**
330-
* @var string
331-
*/
332-
private $prop1 = '';
367+
new PropertyAssembler(PropertyAssemblerOptions::create()->withDefaultValues(false))
333368
```
334369

335370
## RequestAssembler

src/Phpro/SoapClient/CodeGenerator/Assembler/ConstructorAssembler.php

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
use Laminas\Code\Generator\ParameterGenerator;
77
use Phpro\SoapClient\CodeGenerator\Context\ContextInterface;
88
use Phpro\SoapClient\CodeGenerator\Context\TypeContext;
9+
use Phpro\SoapClient\CodeGenerator\Model\Property;
910
use Phpro\SoapClient\CodeGenerator\Model\Type;
11+
use Phpro\SoapClient\CodeGenerator\Provider\ScalarDefaultProvider;
1012
use Phpro\SoapClient\Exception\AssemblerException;
1113
use Laminas\Code\Generator\MethodGenerator;
14+
use Soap\Engine\Metadata\Model\TypeMeta;
1215

1316
/**
1417
* Class ConstructorAssembler
@@ -67,23 +70,30 @@ public function assemble(ContextInterface $context)
6770
*/
6871
private function assembleConstructor(Type $type): MethodGenerator
6972
{
70-
$body = [];
7173
$constructor = (new MethodGenerator('__construct'))
7274
->setVisibility(MethodGenerator::VISIBILITY_PUBLIC);
7375

7476
$docblock = (new DocBlockGenerator())
7577
->setWordWrap(false)
7678
->setShortDescription('Constructor');
7779

78-
foreach ($type->getProperties() as $property) {
79-
$param = (new ParameterGenerator($property->getName()));
80-
$body[] = sprintf('$this->%1$s = $%1$s;', $property->getName());
80+
$entries = $this->resolveProperties($type->getProperties());
81+
82+
$body = [];
83+
foreach ($entries as $entry) {
84+
$property = $entry['property'];
85+
$param = new ParameterGenerator($property->getName());
8186

8287
if ($this->options->useTypeHints()) {
8388
$param->setType($property->getPhpType());
8489
}
8590

91+
if ($entry['hasDefault']) {
92+
$param->setDefaultValue($entry['default']);
93+
}
94+
8695
$constructor->setParameter($param);
96+
$body[] = sprintf('$this->%1$s = $%1$s;', $property->getName());
8797

8898
if ($this->options->useDocBlocks()) {
8999
$docblock->setTag([
@@ -101,4 +111,47 @@ private function assembleConstructor(Type $type): MethodGenerator
101111

102112
return $constructor;
103113
}
114+
115+
/**
116+
* Resolves properties by applying optionalValue nullability, computing default values
117+
* via ScalarDefaultProvider, and reordering so that properties without defaults come
118+
* first (PHP requirement for parameters with defaults to be trailing).
119+
*
120+
* @param list<Property> $properties
121+
* @return list<array{property: Property, default: mixed, hasDefault: bool}>
122+
*/
123+
private function resolveProperties(array $properties): array
124+
{
125+
$applyDefaults = ($this->options->useDefaultValues() || $this->options->useOptionalValue())
126+
&& $this->options->useTypeHints();
127+
128+
$defaultProvider = new ScalarDefaultProvider();
129+
$entries = [];
130+
131+
foreach ($properties as $property) {
132+
if ($this->options->useOptionalValue()) {
133+
$property = $property->withMeta(fn(TypeMeta $meta): TypeMeta => $meta->withIsNullable(true));
134+
}
135+
136+
$hasDefault = false;
137+
$default = null;
138+
if ($applyDefaults) {
139+
$result = $defaultProvider($property);
140+
if ($result->isSucceeded()) {
141+
$hasDefault = true;
142+
$default = $result->getResult();
143+
}
144+
}
145+
146+
$entries[] = ['property' => $property, 'default' => $default, 'hasDefault' => $hasDefault];
147+
}
148+
149+
if ($applyDefaults) {
150+
$withoutDefaults = array_filter($entries, static fn (array $e): bool => !$e['hasDefault']);
151+
$withDefaults = array_filter($entries, static fn (array $e): bool => $e['hasDefault']);
152+
$entries = [...array_values($withoutDefaults), ...array_values($withDefaults)];
153+
}
154+
155+
return $entries;
156+
}
104157
}

src/Phpro/SoapClient/CodeGenerator/Assembler/ConstructorAssemblerOptions.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@ class ConstructorAssemblerOptions
2121
*/
2222
private $docBlocks = true;
2323

24+
/**
25+
* When enabled, constructor parameters receive type-appropriate default values:
26+
* scalars get their zero-value ('', 0, false, 0.0, []), nullable types get null.
27+
* Parameters with defaults are reordered to the end (PHP requirement).
28+
* Requires type hints to be enabled.
29+
*/
30+
private bool $defaultValues = true;
31+
32+
/**
33+
* When enabled, ALL constructor parameters are forced to be nullable with = null,
34+
* regardless of WSDL metadata. Takes precedence over defaultValues.
35+
*/
36+
private bool $optionalValue = false;
37+
2438
/**
2539
* @return ConstructorAssemblerOptions
2640
*/
@@ -70,4 +84,30 @@ public function useDocBlocks(): bool
7084
{
7185
return $this->docBlocks;
7286
}
87+
88+
public function withDefaultValues(bool $withDefaultValues = true): ConstructorAssemblerOptions
89+
{
90+
$new = clone $this;
91+
$new->defaultValues = $withDefaultValues;
92+
93+
return $new;
94+
}
95+
96+
public function useDefaultValues(): bool
97+
{
98+
return $this->defaultValues;
99+
}
100+
101+
public function withOptionalValue(bool $withOptionalValue = true): ConstructorAssemblerOptions
102+
{
103+
$new = clone $this;
104+
$new->optionalValue = $withOptionalValue;
105+
106+
return $new;
107+
}
108+
109+
public function useOptionalValue(): bool
110+
{
111+
return $this->optionalValue;
112+
}
73113
}

src/Phpro/SoapClient/CodeGenerator/Assembler/PropertyAssembler.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
namespace Phpro\SoapClient\CodeGenerator\Assembler;
44

55
use Laminas\Code\Generator\DocBlockGenerator;
6+
use Laminas\Code\Generator\PropertyValueGenerator;
67
use Laminas\Code\Generator\TypeGenerator;
78
use Phpro\SoapClient\CodeGenerator\Context\ContextInterface;
89
use Phpro\SoapClient\CodeGenerator\Context\PropertyContext;
910
use Phpro\SoapClient\CodeGenerator\LaminasCodeFactory\DocBlockGeneratorFactory;
11+
use Phpro\SoapClient\CodeGenerator\Provider\ScalarDefaultProvider;
1012
use Phpro\SoapClient\Exception\AssemblerException;
1113
use Laminas\Code\Generator\PropertyGenerator;
1214
use Soap\Engine\Metadata\Model\TypeMeta;
@@ -80,6 +82,19 @@ public function assemble(ContextInterface $context)
8082
$propertyGenerator->setType(TypeGenerator::fromTypeString($property->getPhpType()));
8183
}
8284

85+
if ($this->options->useDefaultValues()) {
86+
$defaultValue = (new ScalarDefaultProvider())($property);
87+
if ($defaultValue->isSucceeded()) {
88+
$propertyGenerator
89+
->setDefaultValue(
90+
$defaultValue->getResult(),
91+
PropertyValueGenerator::TYPE_AUTO,
92+
PropertyValueGenerator::OUTPUT_SINGLE_LINE
93+
)
94+
->omitDefaultValue(false);
95+
}
96+
}
97+
8398
$class->addPropertyFromGenerator($propertyGenerator);
8499
} catch (\Exception $e) {
85100
throw AssemblerException::fromException($e);

src/Phpro/SoapClient/CodeGenerator/Assembler/PropertyAssemblerOptions.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,26 @@ class PropertyAssemblerOptions
1414
private bool $typeHints = true;
1515
private bool $docBlocks = true;
1616
private string $visibility = PropertyGenerator::VISIBILITY_PRIVATE;
17+
18+
/**
19+
* When enabled, ALL properties are forced to be nullable with = null, regardless of WSDL metadata.
20+
* This makes every property ?Type = null, which is useful when you want to construct
21+
* objects without providing all values upfront.
22+
*
23+
* Takes precedence over `defaultValues`: when both are enabled, all properties become ?Type = null.
24+
*/
1725
private bool $optionalValue = false;
1826

27+
/**
28+
* When enabled, properties receive type-appropriate default values based on WSDL metadata:
29+
* scalars get their zero-value ('', 0, false, 0.0, []), nullable types get null,
30+
* non-nullable complex types get no default.
31+
*
32+
* This differs from `optionalValue`: optionalValue forces ALL properties to ?Type = null
33+
* regardless of WSDL metadata. When both are enabled, optionalValue takes precedence.
34+
*/
35+
private bool $defaultValues = true;
36+
1937
public static function create(): PropertyAssemblerOptions
2038
{
2139
return new self();
@@ -78,4 +96,17 @@ public function useOptionalValue(): bool
7896
{
7997
return $this->optionalValue;
8098
}
99+
100+
public function withDefaultValues(bool $withDefaultValues = true): self
101+
{
102+
$new = clone $this;
103+
$new->defaultValues = $withDefaultValues;
104+
105+
return $new;
106+
}
107+
108+
public function useDefaultValues(): bool
109+
{
110+
return $this->defaultValues;
111+
}
81112
}

src/Phpro/SoapClient/CodeGenerator/Assembler/PropertyDefaultsAssembler.php

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)