Skip to content

Commit bbab4c0

Browse files
authored
Merge pull request #123 from allansun/4.6.x
Add possibility to specify property types for generated properties
2 parents a8ed70e + 23c336d commit bbab4c0

File tree

3 files changed

+131
-54
lines changed

3 files changed

+131
-54
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
/laminas-mkdoc-theme.tgz
66
/laminas-mkdoc-theme/
77
/phpunit.xml
8-
/vendor/
8+
/vendor/

src/Generator/PropertyGenerator.php

Lines changed: 88 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,37 @@ class PropertyGenerator extends AbstractMemberGenerator
2121

2222
protected bool $isConst = false;
2323

24+
protected ?TypeGenerator $type = null;
25+
2426
protected ?PropertyValueGenerator $defaultValue = null;
2527

2628
private bool $omitDefaultValue = false;
2729

30+
/**
31+
* @param PropertyValueGenerator|string|array|null $defaultValue
32+
* @param int|int[] $flags
33+
*/
34+
public function __construct(
35+
?string $name = null,
36+
$defaultValue = null,
37+
$flags = self::FLAG_PUBLIC,
38+
?TypeGenerator $type = null
39+
) {
40+
parent::__construct();
41+
42+
if (null !== $name) {
43+
$this->setName($name);
44+
}
45+
if (null !== $defaultValue) {
46+
$this->setDefaultValue($defaultValue);
47+
}
48+
if ($flags !== self::FLAG_PUBLIC) {
49+
$this->setFlags($flags);
50+
}
51+
52+
$this->type = $type;
53+
}
54+
2855
/** @return static */
2956
public static function fromReflection(PropertyReflection $reflectionProperty)
3057
{
@@ -60,6 +87,11 @@ public static function fromReflection(PropertyReflection $reflectionProperty)
6087
$property->setVisibility(self::VISIBILITY_PUBLIC);
6188
}
6289

90+
$property->setType(TypeGenerator::fromReflectionType(
91+
$reflectionProperty->getType(),
92+
$reflectionProperty->getDeclaringClass()
93+
));
94+
6395
$property->setSourceDirty(false);
6496

6597
return $property;
@@ -68,7 +100,7 @@ public static function fromReflection(PropertyReflection $reflectionProperty)
68100
/**
69101
* Generate from array
70102
*
71-
* @configkey name string [required] Class Name
103+
* @configkey name string [required] Class Name
72104
* @configkey const bool
73105
* @configkey defaultvalue null|bool|string|int|float|array|ValueGenerator
74106
* @configkey flags int
@@ -78,9 +110,10 @@ public static function fromReflection(PropertyReflection $reflectionProperty)
78110
* @configkey visibility string
79111
* @configkey omitdefaultvalue bool
80112
* @configkey readonly bool
81-
* @throws Exception\InvalidArgumentException
82-
* @param array $array
113+
* @configkey type null|TypeGenerator
114+
* @param array $array
83115
* @return static
116+
* @throws Exception\InvalidArgumentException
84117
*/
85118
public static function fromArray(array $array)
86119
{
@@ -136,43 +169,38 @@ public static function fromArray(array $array)
136169

137170
$property->setReadonly($value);
138171
break;
172+
case 'type':
173+
if (! $value instanceof TypeGenerator) {
174+
throw new Exception\InvalidArgumentException(sprintf(
175+
'%s is expecting %s on key %s. Got %s',
176+
__METHOD__,
177+
TypeGenerator::class,
178+
$name,
179+
is_object($value) ? get_class($value) : gettype($value)
180+
));
181+
}
182+
$property->setType($value);
183+
break;
139184
}
140185
}
141186

142187
return $property;
143188
}
144189

145190
/**
146-
* @param PropertyValueGenerator|string|array|null $defaultValue
147-
* @param int|int[] $flags
148-
*/
149-
public function __construct(?string $name = null, $defaultValue = null, $flags = self::FLAG_PUBLIC)
150-
{
151-
parent::__construct();
152-
153-
if (null !== $name) {
154-
$this->setName($name);
155-
}
156-
if (null !== $defaultValue) {
157-
$this->setDefaultValue($defaultValue);
158-
}
159-
if ($flags !== self::FLAG_PUBLIC) {
160-
$this->setFlags($flags);
161-
}
162-
}
163-
164-
/**
165-
* @param bool $const
191+
* @param bool $const
166192
* @return PropertyGenerator
167193
*/
168194
public function setConst($const)
169195
{
170196
if (true === $const) {
171197
$this->setFlags(self::FLAG_CONSTANT);
198+
172199
return $this;
173200
}
174201

175202
$this->removeFlag(self::FLAG_CONSTANT);
203+
176204
return $this;
177205
}
178206

@@ -188,10 +216,12 @@ public function setReadonly(bool $readonly): self
188216
{
189217
if (true === $readonly) {
190218
$this->setFlags(self::FLAG_READONLY);
219+
191220
return $this;
192221
}
193222

194223
$this->removeFlag(self::FLAG_READONLY);
224+
195225
return $this;
196226
}
197227

@@ -221,9 +251,17 @@ public function setFlags($flags)
221251
}
222252

223253
/**
224-
* @param PropertyValueGenerator|mixed $defaultValue
225-
* @param string $defaultValueType
226-
* @param string $defaultValueOutputMode
254+
* @return ?PropertyValueGenerator
255+
*/
256+
public function getDefaultValue()
257+
{
258+
return $this->defaultValue;
259+
}
260+
261+
/**
262+
* @param PropertyValueGenerator|mixed $defaultValue
263+
* @param string $defaultValueType
264+
* @param string $defaultValueOutputMode
227265
* @return static
228266
*/
229267
public function setDefaultValue(
@@ -241,17 +279,9 @@ public function setDefaultValue(
241279
}
242280

243281
/**
244-
* @return ?PropertyValueGenerator
245-
*/
246-
public function getDefaultValue()
247-
{
248-
return $this->defaultValue;
249-
}
250-
251-
/**
252-
* @throws Exception\RuntimeException
253282
* @return string
254283
* @psalm-return non-empty-string
284+
* @throws Exception\RuntimeException
255285
*/
256286
public function generate()
257287
{
@@ -273,20 +303,23 @@ public function generate()
273303
$this->name
274304
));
275305
}
306+
276307
return $output
277-
. $this->indentation
278-
. ($this->isFinal() ? 'final ' : '')
279-
. $this->getVisibility()
280-
. ' const '
281-
. $name . ' = '
282-
. ($defaultValue !== null ? $defaultValue->generate() : 'null;');
308+
. $this->indentation
309+
. ($this->isFinal() ? 'final ' : '')
310+
. $this->getVisibility()
311+
. ' const '
312+
. $name . ' = '
313+
. ($defaultValue !== null ? $defaultValue->generate() : 'null;');
283314
}
284315

316+
$type = $this->type;
285317
$output .= $this->indentation
286-
. $this->getVisibility()
287-
. ($this->isReadonly() ? ' readonly' : '')
288-
. ($this->isStatic() ? ' static' : '')
289-
. ' $' . $name;
318+
. $this->getVisibility()
319+
. ($this->isReadonly() ? ' readonly' : '')
320+
. ($this->isStatic() ? ' static' : '')
321+
. ($type ? ' ' . $type->generate() : '')
322+
. ' $' . $name;
290323

291324
if ($this->omitDefaultValue) {
292325
return $output . ';';
@@ -304,4 +337,14 @@ public function omitDefaultValue(bool $omit = true)
304337

305338
return $this;
306339
}
340+
341+
public function getType(): ?TypeGenerator
342+
{
343+
return $this->type;
344+
}
345+
346+
public function setType(?TypeGenerator $type): void
347+
{
348+
$this->type = $type;
349+
}
307350
}

test/Generator/PropertyGeneratorTest.php

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@
55
use Generator;
66
use Laminas\Code\Generator\DocBlock\Tag\VarTag;
77
use Laminas\Code\Generator\DocBlockGenerator;
8+
use Laminas\Code\Generator\Exception\InvalidArgumentException;
89
use Laminas\Code\Generator\Exception\RuntimeException;
910
use Laminas\Code\Generator\PropertyGenerator;
1011
use Laminas\Code\Generator\PropertyValueGenerator;
12+
use Laminas\Code\Generator\TypeGenerator;
1113
use Laminas\Code\Generator\ValueGenerator;
1214
use Laminas\Code\Reflection\ClassReflection;
1315
use Laminas\Code\Reflection\PropertyReflection;
1416
use LaminasTest\Code\Generator\TestAsset\ClassWithTypedProperty;
15-
use PHP_CodeSniffer\Tokenizers\PHP;
1617
use PHPUnit\Framework\TestCase;
1718
use ReflectionProperty;
1819
use stdClass;
1920

21+
use function addslashes;
2022
use function array_shift;
2123
use function str_replace;
2224
use function uniqid;
@@ -55,7 +57,7 @@ public function dataSetTypeSetValueGenerate(): array
5557

5658
/**
5759
* @dataProvider dataSetTypeSetValueGenerate
58-
* @param mixed $value
60+
* @param mixed $value
5961
*/
6062
public function testSetTypeSetValueGenerate(string $type, $value, string $code): void
6163
{
@@ -69,7 +71,7 @@ public function testSetTypeSetValueGenerate(string $type, $value, string $code):
6971

7072
/**
7173
* @dataProvider dataSetTypeSetValueGenerate
72-
* @param mixed $value
74+
* @param mixed $value
7375
*/
7476
public function testSetBogusTypeSetValueGenerateUseAutoDetection(string $type, $value, string $code): void
7577
{
@@ -295,6 +297,7 @@ public function testCreateFromArray(): void
295297
'static' => true,
296298
'visibility' => PropertyGenerator::VISIBILITY_PROTECTED,
297299
'omitdefaultvalue' => true,
300+
'type' => TypeGenerator::fromTypeString(self::class),
298301
]);
299302

300303
self::assertSame('SampleProperty', $propertyGenerator->getName());
@@ -307,7 +310,8 @@ public function testCreateFromArray(): void
307310
self::assertTrue($propertyGenerator->isStatic());
308311
self::assertSame(PropertyGenerator::VISIBILITY_PROTECTED, $propertyGenerator->getVisibility());
309312
self::assertStringNotContainsString('default-foo', $propertyGenerator->generate());
310-
313+
self::assertEquals(self::class, $propertyGenerator->getType());
314+
self::assertInstanceOf(TypeGenerator::class, $propertyGenerator->getType());
311315
$reflectionOmitDefaultValue = new ReflectionProperty($propertyGenerator, 'omitDefaultValue');
312316

313317
$reflectionOmitDefaultValue->setAccessible(true);
@@ -355,7 +359,7 @@ public function testPropertyDocBlockWillLoadFromReflection(): void
355359

356360
/**
357361
* @dataProvider dataSetTypeSetValueGenerate
358-
* @param mixed $value
362+
* @param mixed $value
359363
*/
360364
public function testSetDefaultValue(string $type, $value): void
361365
{
@@ -387,14 +391,14 @@ public function testFromReflectionOmitsDefaultValueIfItIsNull(): void
387391
$this->assertSame(' public static $fooStaticProperty;', $code);
388392
}
389393

390-
public function testFromReflectionOmitsTypeHintInTypedProperty(): void
394+
public function testFromReflectionWithTypeHintInTypedProperty(): void
391395
{
392396
$reflectionProperty = new PropertyReflection(ClassWithTypedProperty::class, 'typedProperty');
393397

394398
$generator = PropertyGenerator::fromReflection($reflectionProperty);
395399
$code = $generator->generate();
396400

397-
self::assertSame(' private $typedProperty;', $code);
401+
self::assertSame(' private string $typedProperty;', $code);
398402
}
399403

400404
/** @requires PHP >= 8.1 */
@@ -409,6 +413,36 @@ public function testFromReflectionReadonlyProperty(): void
409413
$generator = PropertyGenerator::fromReflection($reflectionProperty);
410414
$code = $generator->generate();
411415

412-
self::assertSame(' public readonly $readonly;', $code);
416+
self::assertSame(' public readonly string $readonly;', $code);
417+
}
418+
419+
public function testPropertyCanProduceTypeHinting(): void
420+
{
421+
$codeGenProperty = new PropertyGenerator('someVal', 'value', [], TypeGenerator::fromTypeString('SomeClass'));
422+
self::assertSame(' public \SomeClass $someVal = \'value\';', $codeGenProperty->generate());
423+
}
424+
425+
public function testFromArrayWithIncorrectTypePassed(): void
426+
{
427+
$this->expectException(InvalidArgumentException::class);
428+
$this->expectExceptionMessageMatches('/is expecting ' . addslashes(TypeGenerator::class) . '/');
429+
430+
PropertyGenerator::fromArray([
431+
'name' => 'someVal',
432+
'type' => 'invalidStringn',
433+
]);
434+
}
435+
436+
public function testCanChangeTypeForPropertyGenerator(): void
437+
{
438+
$property = new PropertyGenerator('p', null, [], TypeGenerator::fromTypeString('?string'));
439+
self::assertSame(' public ?string $p = null;', $property->generate());
440+
441+
$property->setType(null);
442+
self::assertSame(
443+
' public $p = null;',
444+
$property->generate(),
445+
'A property type can be dropped'
446+
);
413447
}
414448
}

0 commit comments

Comments
 (0)