Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement property and constructor generation

+801 -1
+177
src/Generator/ConstructorGenerator.php
··· 1 + <?php 2 + 3 + namespace SocialDept\Schema\Generator; 4 + 5 + class ConstructorGenerator 6 + { 7 + /** 8 + * Property generator instance. 9 + */ 10 + protected PropertyGenerator $propertyGenerator; 11 + 12 + /** 13 + * Stub renderer instance. 14 + */ 15 + protected StubRenderer $renderer; 16 + 17 + /** 18 + * Create a new ConstructorGenerator. 19 + */ 20 + public function __construct( 21 + ?PropertyGenerator $propertyGenerator = null, 22 + ?StubRenderer $renderer = null 23 + ) { 24 + $this->propertyGenerator = $propertyGenerator ?? new PropertyGenerator; 25 + $this->renderer = $renderer ?? new StubRenderer; 26 + } 27 + 28 + /** 29 + * Generate constructor with promoted properties. 30 + * 31 + * @param array<string, array<string, mixed>> $properties 32 + * @param array<string> $required 33 + */ 34 + public function generate(array $properties, array $required = []): string 35 + { 36 + if (empty($properties)) { 37 + return $this->generateEmpty(); 38 + } 39 + 40 + $parameters = $this->generateParameters($properties, $required); 41 + $body = $this->generateBody($properties, $required); 42 + 43 + return $this->renderer->render('constructor', [ 44 + 'docBlock' => $this->generateDocBlock($properties, $required), 45 + 'parameters' => $parameters, 46 + 'body' => $body, 47 + ]); 48 + } 49 + 50 + /** 51 + * Generate empty constructor. 52 + */ 53 + protected function generateEmpty(): string 54 + { 55 + return ''; 56 + } 57 + 58 + /** 59 + * Generate constructor parameters. 60 + * 61 + * @param array<string, array<string, mixed>> $properties 62 + * @param array<string> $required 63 + */ 64 + protected function generateParameters(array $properties, array $required = []): string 65 + { 66 + $params = []; 67 + 68 + foreach ($properties as $name => $definition) { 69 + $promoted = $this->propertyGenerator->generatePromoted($name, $definition, $required); 70 + $params[] = ' '.$promoted.','; 71 + } 72 + 73 + // Remove trailing comma from last parameter 74 + if (! empty($params)) { 75 + $params[count($params) - 1] = rtrim($params[count($params) - 1], ','); 76 + } 77 + 78 + return implode("\n", $params); 79 + } 80 + 81 + /** 82 + * Generate constructor body. 83 + * 84 + * @param array<string, array<string, mixed>> $properties 85 + * @param array<string> $required 86 + */ 87 + protected function generateBody(array $properties, array $required = []): string 88 + { 89 + // For promoted properties, constructor body is usually empty 90 + // But we can add validation or initialization logic here if needed 91 + return ''; 92 + } 93 + 94 + /** 95 + * Generate constructor documentation block. 96 + * 97 + * @param array<string, array<string, mixed>> $properties 98 + * @param array<string> $required 99 + */ 100 + protected function generateDocBlock(array $properties, array $required = []): string 101 + { 102 + if (empty($properties)) { 103 + return ''; 104 + } 105 + 106 + $lines = [' /**']; 107 + $lines[] = ' * Create a new instance.'; 108 + 109 + // Add @param tags for each parameter 110 + foreach ($properties as $name => $definition) { 111 + $docType = $this->propertyGenerator->getDocType( 112 + $definition, 113 + ! in_array($name, $required) 114 + ); 115 + $description = $definition['description'] ?? null; 116 + 117 + if ($description) { 118 + $lines[] = ' * @param '.$docType.' $'.$name.' '.$description; 119 + } else { 120 + $lines[] = ' * @param '.$docType.' $'.$name; 121 + } 122 + } 123 + 124 + $lines[] = ' */'; 125 + 126 + return implode("\n", $lines); 127 + } 128 + 129 + /** 130 + * Generate constructor with assignments (non-promoted). 131 + * 132 + * @param array<string, array<string, mixed>> $properties 133 + * @param array<string> $required 134 + */ 135 + public function generateWithAssignments(array $properties, array $required = []): string 136 + { 137 + if (empty($properties)) { 138 + return $this->generateEmpty(); 139 + } 140 + 141 + $parameters = []; 142 + $assignments = []; 143 + 144 + foreach ($properties as $name => $definition) { 145 + $signature = $this->propertyGenerator->generateSignature($name, $definition, $required); 146 + $parameters[] = ' '.$signature.','; 147 + $assignments[] = ' $this->'.$name.' = $'.$name.';'; 148 + } 149 + 150 + // Remove trailing comma from last parameter 151 + if (! empty($parameters)) { 152 + $parameters[count($parameters) - 1] = rtrim($parameters[count($parameters) - 1], ','); 153 + } 154 + 155 + $params = implode("\n", $parameters); 156 + $body = implode("\n", $assignments); 157 + 158 + return " /**\n". 159 + " * Create a new instance.\n". 160 + " */\n". 161 + " public function __construct(\n". 162 + $params."\n". 163 + " ) {\n". 164 + $body."\n". 165 + " }"; 166 + } 167 + 168 + /** 169 + * Check if constructor should be generated. 170 + * 171 + * @param array<string, array<string, mixed>> $properties 172 + */ 173 + public function shouldGenerate(array $properties): bool 174 + { 175 + return ! empty($properties); 176 + } 177 + }
+167
src/Generator/PropertyGenerator.php
··· 1 + <?php 2 + 3 + namespace SocialDept\Schema\Generator; 4 + 5 + class PropertyGenerator 6 + { 7 + /** 8 + * Type mapper instance. 9 + */ 10 + protected TypeMapper $typeMapper; 11 + 12 + /** 13 + * Stub renderer instance. 14 + */ 15 + protected StubRenderer $renderer; 16 + 17 + /** 18 + * Create a new PropertyGenerator. 19 + */ 20 + public function __construct(?TypeMapper $typeMapper = null, ?StubRenderer $renderer = null) 21 + { 22 + $this->typeMapper = $typeMapper ?? new TypeMapper; 23 + $this->renderer = $renderer ?? new StubRenderer; 24 + } 25 + 26 + /** 27 + * Generate a single property. 28 + * 29 + * @param array<string, mixed> $definition 30 + * @param array<string> $required 31 + */ 32 + public function generate(string $name, array $definition, array $required = []): string 33 + { 34 + $isRequired = in_array($name, $required); 35 + $phpType = $this->typeMapper->toPhpType($definition, ! $isRequired); 36 + $docType = $this->typeMapper->toPhpDocType($definition, ! $isRequired); 37 + $description = $definition['description'] ?? null; 38 + $default = $this->getDefaultValue($definition, $isRequired); 39 + 40 + return $this->renderer->render('property', [ 41 + 'docBlock' => $this->generateDocBlock($description, $docType), 42 + 'visibility' => 'public ', 43 + 'static' => '', 44 + 'readonly' => 'readonly ', 45 + 'type' => $phpType, 46 + 'name' => $name, 47 + 'default' => $default, 48 + ]); 49 + } 50 + 51 + /** 52 + * Generate multiple properties. 53 + * 54 + * @param array<string, array<string, mixed>> $properties 55 + * @param array<string> $required 56 + * @return array<string> 57 + */ 58 + public function generateMultiple(array $properties, array $required = []): array 59 + { 60 + $result = []; 61 + 62 + foreach ($properties as $name => $definition) { 63 + $result[] = $this->generate($name, $definition, $required); 64 + } 65 + 66 + return $result; 67 + } 68 + 69 + /** 70 + * Generate property documentation block. 71 + */ 72 + protected function generateDocBlock(?string $description, string $type): string 73 + { 74 + $lines = ['/**']; 75 + 76 + if ($description) { 77 + $lines[] = ' * '.$description; 78 + $lines[] = ' *'; 79 + } 80 + 81 + $lines[] = ' * @var '.$type; 82 + $lines[] = ' */'; 83 + 84 + return implode("\n", $lines); 85 + } 86 + 87 + /** 88 + * Get default value for property. 89 + * 90 + * @param array<string, mixed> $definition 91 + */ 92 + protected function getDefaultValue(array $definition, bool $isRequired): string 93 + { 94 + if ($isRequired) { 95 + return ''; 96 + } 97 + 98 + $default = $this->typeMapper->getDefaultValue($definition); 99 + 100 + if ($default !== null) { 101 + return ' = '.$default; 102 + } 103 + 104 + return ''; 105 + } 106 + 107 + /** 108 + * Generate property signature (for constructor parameters). 109 + * 110 + * @param array<string, mixed> $definition 111 + * @param array<string> $required 112 + */ 113 + public function generateSignature(string $name, array $definition, array $required = []): string 114 + { 115 + $isRequired = in_array($name, $required); 116 + $phpType = $this->typeMapper->toPhpType($definition, ! $isRequired); 117 + $default = $this->getDefaultValue($definition, $isRequired); 118 + 119 + return $phpType.' $'.$name.$default; 120 + } 121 + 122 + /** 123 + * Generate promoted property signature (for constructor). 124 + * 125 + * @param array<string, mixed> $definition 126 + * @param array<string> $required 127 + */ 128 + public function generatePromoted(string $name, array $definition, array $required = []): string 129 + { 130 + $isRequired = in_array($name, $required); 131 + $phpType = $this->typeMapper->toPhpType($definition, ! $isRequired); 132 + $default = $this->getDefaultValue($definition, $isRequired); 133 + 134 + return 'public readonly '.$phpType.' $'.$name.$default; 135 + } 136 + 137 + /** 138 + * Check if property should be nullable. 139 + * 140 + * @param array<string, mixed> $definition 141 + * @param array<string> $required 142 + */ 143 + public function isNullable(string $name, array $definition, array $required = []): bool 144 + { 145 + return ! in_array($name, $required); 146 + } 147 + 148 + /** 149 + * Get property type. 150 + * 151 + * @param array<string, mixed> $definition 152 + */ 153 + public function getType(array $definition, bool $nullable = false): string 154 + { 155 + return $this->typeMapper->toPhpType($definition, $nullable); 156 + } 157 + 158 + /** 159 + * Get property doc type. 160 + * 161 + * @param array<string, mixed> $definition 162 + */ 163 + public function getDocType(array $definition, bool $nullable = false): string 164 + { 165 + return $this->typeMapper->toPhpDocType($definition, $nullable); 166 + } 167 + }
+1 -1
stubs/property.stub
··· 1 1 {{ docBlock }} 2 - {{ visibility }}{{ static }}{{ readonly }} {{ type }} ${{ name }}{{ default }}; 2 + {{ visibility }}{{ static }}{{ readonly }}{{ type }} ${{ name }}{{ default }};
+244
tests/Unit/Generator/ConstructorGeneratorTest.php
··· 1 + <?php 2 + 3 + namespace SocialDept\Schema\Tests\Unit\Generator; 4 + 5 + use Orchestra\Testbench\TestCase; 6 + use SocialDept\Schema\Generator\ConstructorGenerator; 7 + 8 + class ConstructorGeneratorTest extends TestCase 9 + { 10 + protected ConstructorGenerator $generator; 11 + 12 + protected function setUp(): void 13 + { 14 + parent::setUp(); 15 + 16 + $this->generator = new ConstructorGenerator; 17 + } 18 + 19 + public function test_it_generates_constructor_with_promoted_properties(): void 20 + { 21 + $constructor = $this->generator->generate( 22 + [ 23 + 'name' => ['type' => 'string'], 24 + 'age' => ['type' => 'integer'], 25 + ], 26 + ['name', 'age'] 27 + ); 28 + 29 + $this->assertStringContainsString('public function __construct(', $constructor); 30 + $this->assertStringContainsString('public readonly string $name', $constructor); 31 + $this->assertStringContainsString('public readonly int $age', $constructor); 32 + } 33 + 34 + public function test_it_handles_optional_parameters(): void 35 + { 36 + $constructor = $this->generator->generate( 37 + [ 38 + 'name' => ['type' => 'string'], 39 + 'nickname' => ['type' => 'string'], 40 + ], 41 + ['name'] 42 + ); 43 + 44 + $this->assertStringContainsString('public readonly string $name', $constructor); 45 + $this->assertStringContainsString('public readonly ?string $nickname', $constructor); 46 + } 47 + 48 + public function test_it_returns_empty_for_no_properties(): void 49 + { 50 + $constructor = $this->generator->generate([], []); 51 + 52 + $this->assertEmpty($constructor); 53 + } 54 + 55 + public function test_it_generates_docblock(): void 56 + { 57 + $constructor = $this->generator->generate( 58 + [ 59 + 'name' => ['type' => 'string'], 60 + ], 61 + ['name'] 62 + ); 63 + 64 + $this->assertStringContainsString('/**', $constructor); 65 + $this->assertStringContainsString('* Create a new instance.', $constructor); 66 + $this->assertStringContainsString('* @param string $name', $constructor); 67 + } 68 + 69 + public function test_it_includes_descriptions_in_docblock(): void 70 + { 71 + $constructor = $this->generator->generate( 72 + [ 73 + 'name' => [ 74 + 'type' => 'string', 75 + 'description' => 'The user name', 76 + ], 77 + ], 78 + ['name'] 79 + ); 80 + 81 + $this->assertStringContainsString('The user name', $constructor); 82 + } 83 + 84 + public function test_it_handles_multiple_properties(): void 85 + { 86 + $constructor = $this->generator->generate( 87 + [ 88 + 'id' => ['type' => 'string'], 89 + 'name' => ['type' => 'string'], 90 + 'email' => ['type' => 'string'], 91 + 'age' => ['type' => 'integer'], 92 + 'active' => ['type' => 'boolean'], 93 + ], 94 + ['id', 'name', 'email'] 95 + ); 96 + 97 + $this->assertStringContainsString('$id', $constructor); 98 + $this->assertStringContainsString('$name', $constructor); 99 + $this->assertStringContainsString('$email', $constructor); 100 + $this->assertStringContainsString('$age', $constructor); 101 + $this->assertStringContainsString('$active', $constructor); 102 + } 103 + 104 + public function test_it_generates_with_assignments(): void 105 + { 106 + $constructor = $this->generator->generateWithAssignments( 107 + [ 108 + 'name' => ['type' => 'string'], 109 + 'age' => ['type' => 'integer'], 110 + ], 111 + ['name', 'age'] 112 + ); 113 + 114 + $this->assertStringContainsString('public function __construct(', $constructor); 115 + $this->assertStringContainsString('string $name,', $constructor); 116 + $this->assertStringContainsString('int $age', $constructor); 117 + $this->assertStringContainsString('$this->name = $name;', $constructor); 118 + $this->assertStringContainsString('$this->age = $age;', $constructor); 119 + } 120 + 121 + public function test_it_checks_if_constructor_should_be_generated(): void 122 + { 123 + $this->assertTrue($this->generator->shouldGenerate(['name' => ['type' => 'string']])); 124 + $this->assertFalse($this->generator->shouldGenerate([])); 125 + } 126 + 127 + public function test_it_handles_default_values(): void 128 + { 129 + $constructor = $this->generator->generate( 130 + [ 131 + 'status' => [ 132 + 'type' => 'string', 133 + 'default' => 'active', 134 + ], 135 + ], 136 + [] 137 + ); 138 + 139 + $this->assertStringContainsString("= 'active'", $constructor); 140 + } 141 + 142 + public function test_it_handles_nullable_with_null_default(): void 143 + { 144 + $constructor = $this->generator->generate( 145 + [ 146 + 'middleName' => [ 147 + 'type' => 'string', 148 + 'default' => null, 149 + ], 150 + ], 151 + [] 152 + ); 153 + 154 + $this->assertStringContainsString('?string $middleName = null', $constructor); 155 + } 156 + 157 + public function test_it_handles_all_primitive_types(): void 158 + { 159 + $constructor = $this->generator->generate( 160 + [ 161 + 'name' => ['type' => 'string'], 162 + 'age' => ['type' => 'integer'], 163 + 'price' => ['type' => 'number'], 164 + 'active' => ['type' => 'boolean'], 165 + 'tags' => ['type' => 'array'], 166 + ], 167 + ['name', 'age', 'price', 'active', 'tags'] 168 + ); 169 + 170 + $this->assertStringContainsString('string $name', $constructor); 171 + $this->assertStringContainsString('int $age', $constructor); 172 + $this->assertStringContainsString('float $price', $constructor); 173 + $this->assertStringContainsString('bool $active', $constructor); 174 + $this->assertStringContainsString('array $tags', $constructor); 175 + } 176 + 177 + public function test_it_does_not_add_trailing_comma_to_last_parameter(): void 178 + { 179 + $constructor = $this->generator->generate( 180 + [ 181 + 'first' => ['type' => 'string'], 182 + 'last' => ['type' => 'string'], 183 + ], 184 + ['first', 'last'] 185 + ); 186 + 187 + // Last parameter should not have a trailing comma 188 + $this->assertStringNotContainsString('$last,', $constructor); 189 + $this->assertStringContainsString('$last', $constructor); 190 + } 191 + 192 + public function test_it_formats_parameters_with_proper_indentation(): void 193 + { 194 + $constructor = $this->generator->generate( 195 + [ 196 + 'name' => ['type' => 'string'], 197 + ], 198 + ['name'] 199 + ); 200 + 201 + // Check for proper indentation (8 spaces for parameters) 202 + $this->assertStringContainsString(' public readonly string $name', $constructor); 203 + } 204 + 205 + public function test_it_generates_empty_body_for_promoted_properties(): void 206 + { 207 + $constructor = $this->generator->generate( 208 + [ 209 + 'name' => ['type' => 'string'], 210 + ], 211 + ['name'] 212 + ); 213 + 214 + // Constructor body should be empty for promoted properties 215 + $this->assertMatchesRegularExpression('/\) \{\s+\}/', $constructor); 216 + } 217 + 218 + public function test_it_handles_blob_type(): void 219 + { 220 + $constructor = $this->generator->generate( 221 + [ 222 + 'image' => ['type' => 'blob'], 223 + ], 224 + [] 225 + ); 226 + 227 + $this->assertStringContainsString('BlobReference', $constructor); 228 + } 229 + 230 + public function test_it_handles_ref_type(): void 231 + { 232 + $constructor = $this->generator->generate( 233 + [ 234 + 'author' => [ 235 + 'type' => 'ref', 236 + 'ref' => 'app.test.author', 237 + ], 238 + ], 239 + ['author'] 240 + ); 241 + 242 + $this->assertStringContainsString('App\\Lexicon\\Test\\App\\Author', $constructor); 243 + } 244 + }
+212
tests/Unit/Generator/PropertyGeneratorTest.php
··· 1 + <?php 2 + 3 + namespace SocialDept\Schema\Tests\Unit\Generator; 4 + 5 + use Orchestra\Testbench\TestCase; 6 + use SocialDept\Schema\Generator\PropertyGenerator; 7 + 8 + class PropertyGeneratorTest extends TestCase 9 + { 10 + protected PropertyGenerator $generator; 11 + 12 + protected function setUp(): void 13 + { 14 + parent::setUp(); 15 + 16 + $this->generator = new PropertyGenerator; 17 + } 18 + 19 + public function test_it_generates_required_string_property(): void 20 + { 21 + $property = $this->generator->generate('name', ['type' => 'string'], ['name']); 22 + 23 + $this->assertStringContainsString('public readonly string $name;', $property); 24 + $this->assertStringContainsString('@var string', $property); 25 + } 26 + 27 + public function test_it_generates_optional_string_property(): void 28 + { 29 + $property = $this->generator->generate('nickname', ['type' => 'string'], []); 30 + 31 + $this->assertStringContainsString('public readonly ?string $nickname;', $property); 32 + $this->assertStringContainsString('@var string|null', $property); 33 + } 34 + 35 + public function test_it_generates_integer_property(): void 36 + { 37 + $property = $this->generator->generate('age', ['type' => 'integer'], ['age']); 38 + 39 + $this->assertStringContainsString('public readonly int $age;', $property); 40 + $this->assertStringContainsString('@var int', $property); 41 + } 42 + 43 + public function test_it_generates_boolean_property(): void 44 + { 45 + $property = $this->generator->generate('active', ['type' => 'boolean'], ['active']); 46 + 47 + $this->assertStringContainsString('public readonly bool $active;', $property); 48 + $this->assertStringContainsString('@var bool', $property); 49 + } 50 + 51 + public function test_it_generates_array_property(): void 52 + { 53 + $property = $this->generator->generate('tags', ['type' => 'array'], ['tags']); 54 + 55 + $this->assertStringContainsString('public readonly array $tags;', $property); 56 + $this->assertStringContainsString('@var array', $property); 57 + } 58 + 59 + public function test_it_includes_description_in_docblock(): void 60 + { 61 + $property = $this->generator->generate( 62 + 'name', 63 + [ 64 + 'type' => 'string', 65 + 'description' => 'The user name', 66 + ], 67 + ['name'] 68 + ); 69 + 70 + $this->assertStringContainsString('* The user name', $property); 71 + } 72 + 73 + public function test_it_generates_property_with_default_value(): void 74 + { 75 + $property = $this->generator->generate( 76 + 'status', 77 + [ 78 + 'type' => 'string', 79 + 'default' => 'active', 80 + ], 81 + [] 82 + ); 83 + 84 + $this->assertStringContainsString("= 'active'", $property); 85 + } 86 + 87 + public function test_it_generates_multiple_properties(): void 88 + { 89 + $properties = $this->generator->generateMultiple( 90 + [ 91 + 'name' => ['type' => 'string'], 92 + 'age' => ['type' => 'integer'], 93 + ], 94 + ['name', 'age'] 95 + ); 96 + 97 + $this->assertCount(2, $properties); 98 + $this->assertStringContainsString('$name', $properties[0]); 99 + $this->assertStringContainsString('$age', $properties[1]); 100 + } 101 + 102 + public function test_it_generates_property_signature(): void 103 + { 104 + $signature = $this->generator->generateSignature('name', ['type' => 'string'], ['name']); 105 + 106 + $this->assertSame('string $name', $signature); 107 + } 108 + 109 + public function test_it_generates_optional_property_signature(): void 110 + { 111 + $signature = $this->generator->generateSignature('nickname', ['type' => 'string'], []); 112 + 113 + $this->assertSame('?string $nickname', $signature); 114 + } 115 + 116 + public function test_it_generates_promoted_property(): void 117 + { 118 + $promoted = $this->generator->generatePromoted('name', ['type' => 'string'], ['name']); 119 + 120 + $this->assertSame('public readonly string $name', $promoted); 121 + } 122 + 123 + public function test_it_generates_optional_promoted_property(): void 124 + { 125 + $promoted = $this->generator->generatePromoted('nickname', ['type' => 'string'], []); 126 + 127 + $this->assertSame('public readonly ?string $nickname', $promoted); 128 + } 129 + 130 + public function test_it_checks_if_property_is_nullable(): void 131 + { 132 + $this->assertFalse($this->generator->isNullable('name', ['type' => 'string'], ['name'])); 133 + $this->assertTrue($this->generator->isNullable('nickname', ['type' => 'string'], [])); 134 + } 135 + 136 + public function test_it_gets_property_type(): void 137 + { 138 + $type = $this->generator->getType(['type' => 'string']); 139 + 140 + $this->assertSame('string', $type); 141 + } 142 + 143 + public function test_it_gets_nullable_property_type(): void 144 + { 145 + $type = $this->generator->getType(['type' => 'string'], true); 146 + 147 + $this->assertSame('?string', $type); 148 + } 149 + 150 + public function test_it_gets_property_doc_type(): void 151 + { 152 + $docType = $this->generator->getDocType(['type' => 'string']); 153 + 154 + $this->assertSame('string', $docType); 155 + } 156 + 157 + public function test_it_gets_nullable_property_doc_type(): void 158 + { 159 + $docType = $this->generator->getDocType(['type' => 'string'], true); 160 + 161 + $this->assertSame('string|null', $docType); 162 + } 163 + 164 + public function test_it_handles_blob_type(): void 165 + { 166 + $property = $this->generator->generate('image', ['type' => 'blob'], []); 167 + 168 + $this->assertStringContainsString('BlobReference', $property); 169 + } 170 + 171 + public function test_it_handles_ref_type(): void 172 + { 173 + $property = $this->generator->generate( 174 + 'author', 175 + [ 176 + 'type' => 'ref', 177 + 'ref' => 'app.test.author', 178 + ], 179 + ['author'] 180 + ); 181 + 182 + $this->assertStringContainsString('App\\Lexicon\\Test\\App\\Author', $property); 183 + } 184 + 185 + public function test_it_generates_promoted_with_default(): void 186 + { 187 + $promoted = $this->generator->generatePromoted( 188 + 'status', 189 + [ 190 + 'type' => 'string', 191 + 'default' => 'pending', 192 + ], 193 + [] 194 + ); 195 + 196 + $this->assertStringContainsString('= \'pending\'', $promoted); 197 + } 198 + 199 + public function test_it_handles_number_type(): void 200 + { 201 + $property = $this->generator->generate('price', ['type' => 'number'], ['price']); 202 + 203 + $this->assertStringContainsString('float $price', $property); 204 + } 205 + 206 + public function test_it_handles_mixed_type(): void 207 + { 208 + $property = $this->generator->generate('data', ['type' => 'unknown'], ['data']); 209 + 210 + $this->assertStringContainsString('mixed $data', $property); 211 + } 212 + }