Skip to content

Commit 20dcf5f

Browse files
committed
TASK: Add missing validation for ClassName value object
1 parent 3213cc5 commit 20dcf5f

2 files changed

Lines changed: 240 additions & 3 deletions

File tree

src/Target/Php/TargetSpecific/ClassName.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,32 @@
2424

2525
final class ClassName
2626
{
27+
/**
28+
* as per https://www.php.net/manual/en/language.oop5.basic.php
29+
*/
30+
private const VALID_LABEL_REGEX = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/';
31+
32+
/**
33+
* as per https://www.php.net/manual/en/reserved.other-reserved-words.php
34+
* and https://www.php.net/manual/en/reserved.keywords.php
35+
*/
36+
private const RESERVED_WORDS = [
37+
'int', 'float', 'bool', 'string', 'true', 'false', 'null', 'void',
38+
'iterable', 'object', 'mixed', 'never', 'enum', 'resource', 'numeric',
39+
'__halt_compiler', 'abstract', 'and', 'array', 'as', 'break',
40+
'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue',
41+
'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty',
42+
'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile',
43+
'eval', 'exit', 'extends', 'final', 'finally', 'fn', 'for', 'foreach',
44+
'function', 'global', 'goto', 'if', 'implements', 'include',
45+
'include_once', 'instanceof', 'insteadof', 'interface', 'isset',
46+
'list', 'match', 'namespace', 'new', 'or', 'print', 'private',
47+
'protected', 'public', 'readonly', 'require', 'require_once', 'return',
48+
'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var',
49+
'while', 'xor', 'yield', '__class__', '__dir__', '__file__',
50+
'__function__', '__line__', '__method__', '__namespace__', '__trait__',
51+
];
52+
2753
/**
2854
* @var array<string,ClassName>
2955
*/
@@ -37,6 +63,16 @@ final class ClassName
3763
private function __construct(private readonly string $fullyQualifiedClassName)
3864
{
3965
$this->segments = explode('\\', $this->fullyQualifiedClassName);
66+
67+
foreach ($this->segments as $segment) {
68+
if (preg_match(self::VALID_LABEL_REGEX, $segment) === 0) {
69+
throw new \Exception('@TODO: Invalid class name (due to format)');
70+
}
71+
72+
if (in_array(strtolower($segment) , self::RESERVED_WORDS)) {
73+
throw new \Exception('@TODO: Invalid class name (due to resrerved word)');
74+
}
75+
}
4076
}
4177

4278
public static function fromString(string $string): self

test/Unit/Target/Php/TargetSpecific/ClassNameTest.php

Lines changed: 204 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,213 @@ public function isFlyweight(): void
4848
}
4949

5050
/**
51+
* @return array
52+
*/
53+
public static function invalidClassNameExamples(): array
54+
{
55+
return [
56+
'I am not a valid class name' => ['I am not a valid class name'],
57+
'1AmNotAValidClassName' => ['1AmNotAValidClassName'],
58+
'int' => ['int'],
59+
'float' => ['float'],
60+
'bool' => ['bool'],
61+
'string' => ['string'],
62+
'true' => ['true'],
63+
'false' => ['false'],
64+
'null' => ['null'],
65+
'void' => ['void'],
66+
'iterable' => ['iterable'],
67+
'object' => ['object'],
68+
'mixed' => ['mixed'],
69+
'never' => ['never'],
70+
'enum' => ['enum'],
71+
'resource' => ['resource'],
72+
'numeric' => ['numeric'],
73+
'__halt_compiler' => ['__halt_compiler'],
74+
'abstract' => ['abstract'],
75+
'and' => ['and'],
76+
'array' => ['array'],
77+
'as' => ['as'],
78+
'break' => ['break'],
79+
'callable' => ['callable'],
80+
'case' => ['case'],
81+
'catch' => ['catch'],
82+
'class' => ['class'],
83+
'clone' => ['clone'],
84+
'const' => ['const'],
85+
'continue' => ['continue'],
86+
'declare' => ['declare'],
87+
'default' => ['default'],
88+
'die' => ['die'],
89+
'do' => ['do'],
90+
'echo' => ['echo'],
91+
'else' => ['else'],
92+
'elseif' => ['elseif'],
93+
'empty' => ['empty'],
94+
'enddeclare' => ['enddeclare'],
95+
'endfor' => ['endfor'],
96+
'endforeach' => ['endforeach'],
97+
'endif' => ['endif'],
98+
'endswitch' => ['endswitch'],
99+
'endwhile' => ['endwhile'],
100+
'eval' => ['eval'],
101+
'exit' => ['exit'],
102+
'extends' => ['extends'],
103+
'final' => ['final'],
104+
'finally' => ['finally'],
105+
'fn' => ['fn'],
106+
'for' => ['for'],
107+
'foreach' => ['foreach'],
108+
'function' => ['function'],
109+
'global' => ['global'],
110+
'goto' => ['goto'],
111+
'if' => ['if'],
112+
'implements' => ['implements'],
113+
'include' => ['include'],
114+
'include_once' => ['include_once'],
115+
'instanceof' => ['instanceof'],
116+
'insteadof' => ['insteadof'],
117+
'interface' => ['interface'],
118+
'isset' => ['isset'],
119+
'list' => ['list'],
120+
'match' => ['match'],
121+
'namespace' => ['namespace'],
122+
'new' => ['new'],
123+
'or' => ['or'],
124+
'print' => ['print'],
125+
'private' => ['private'],
126+
'protected' => ['protected'],
127+
'public' => ['public'],
128+
'readonly' => ['readonly'],
129+
'require' => ['require'],
130+
'require_once' => ['require_once'],
131+
'return' => ['return'],
132+
'static' => ['static'],
133+
'switch' => ['switch'],
134+
'throw' => ['throw'],
135+
'trait' => ['trait'],
136+
'try' => ['try'],
137+
'unset' => ['unset'],
138+
'use' => ['use'],
139+
'var' => ['var'],
140+
'while' => ['while'],
141+
'xor' => ['xor'],
142+
'yield' => ['yield'],
143+
'__class__' => ['__class__'],
144+
'__dir__' => ['__dir__'],
145+
'__file__' => ['__file__'],
146+
'__function__' => ['__function__'],
147+
'__line__' => ['__line__'],
148+
'__method__' => ['__method__'],
149+
'__namespace__' => ['__namespace__'],
150+
'__trait__' => ['__trait__'],
151+
'INT' => ['INT'],
152+
'FLOAT' => ['FLOAT'],
153+
'BOOL' => ['BOOL'],
154+
'STRING' => ['STRING'],
155+
'TRUE' => ['TRUE'],
156+
'FALSE' => ['FALSE'],
157+
'NULL' => ['NULL'],
158+
'VOID' => ['VOID'],
159+
'ITERABLE' => ['ITERABLE'],
160+
'OBJECT' => ['OBJECT'],
161+
'MIXED' => ['MIXED'],
162+
'NEVER' => ['NEVER'],
163+
'ENUM' => ['ENUM'],
164+
'RESOURCE' => ['RESOURCE'],
165+
'NUMERIC' => ['NUMERIC'],
166+
'__HALT_COMPILER' => ['__HALT_COMPILER'],
167+
'ABSTRACT' => ['ABSTRACT'],
168+
'AND' => ['AND'],
169+
'ARRAY' => ['ARRAY'],
170+
'AS' => ['AS'],
171+
'BREAK' => ['BREAK'],
172+
'CALLABLE' => ['CALLABLE'],
173+
'CASE' => ['CASE'],
174+
'CATCH' => ['CATCH'],
175+
'CLASS' => ['CLASS'],
176+
'CLONE' => ['CLONE'],
177+
'CONST' => ['CONST'],
178+
'CONTINUE' => ['CONTINUE'],
179+
'DECLARE' => ['DECLARE'],
180+
'DEFAULT' => ['DEFAULT'],
181+
'DIE' => ['DIE'],
182+
'DO' => ['DO'],
183+
'ECHO' => ['ECHO'],
184+
'ELSE' => ['ELSE'],
185+
'ELSEIF' => ['ELSEIF'],
186+
'EMPTY' => ['EMPTY'],
187+
'ENDDECLARE' => ['ENDDECLARE'],
188+
'ENDFOR' => ['ENDFOR'],
189+
'ENDFOREACH' => ['ENDFOREACH'],
190+
'ENDIF' => ['ENDIF'],
191+
'ENDSWITCH' => ['ENDSWITCH'],
192+
'ENDWHILE' => ['ENDWHILE'],
193+
'EVAL' => ['EVAL'],
194+
'EXIT' => ['EXIT'],
195+
'EXTENDS' => ['EXTENDS'],
196+
'FINAL' => ['FINAL'],
197+
'FINALLY' => ['FINALLY'],
198+
'FN' => ['FN'],
199+
'FOR' => ['FOR'],
200+
'FOREACH' => ['FOREACH'],
201+
'FUNCTION' => ['FUNCTION'],
202+
'GLOBAL' => ['GLOBAL'],
203+
'GOTO' => ['GOTO'],
204+
'IF' => ['IF'],
205+
'IMPLEMENTS' => ['IMPLEMENTS'],
206+
'INCLUDE' => ['INCLUDE'],
207+
'INCLUDE_ONCE' => ['INCLUDE_ONCE'],
208+
'INSTANCEOF' => ['INSTANCEOF'],
209+
'INSTEADOF' => ['INSTEADOF'],
210+
'INTERFACE' => ['INTERFACE'],
211+
'ISSET' => ['ISSET'],
212+
'LIST' => ['LIST'],
213+
'MATCH' => ['MATCH'],
214+
'NAMESPACE' => ['NAMESPACE'],
215+
'NEW' => ['NEW'],
216+
'OR' => ['OR'],
217+
'PRINT' => ['PRINT'],
218+
'PRIVATE' => ['PRIVATE'],
219+
'PROTECTED' => ['PROTECTED'],
220+
'PUBLIC' => ['PUBLIC'],
221+
'READONLY' => ['READONLY'],
222+
'REQUIRE' => ['REQUIRE'],
223+
'REQUIRE_ONCE' => ['REQUIRE_ONCE'],
224+
'RETURN' => ['RETURN'],
225+
'STATIC' => ['STATIC'],
226+
'SWITCH' => ['SWITCH'],
227+
'THROW' => ['THROW'],
228+
'TRAIT' => ['TRAIT'],
229+
'TRY' => ['TRY'],
230+
'UNSET' => ['UNSET'],
231+
'USE' => ['USE'],
232+
'VAR' => ['VAR'],
233+
'WHILE' => ['WHILE'],
234+
'XOR' => ['XOR'],
235+
'YIELD' => ['YIELD'],
236+
'__CLASS__' => ['__CLASS__'],
237+
'__DIR__' => ['__DIR__'],
238+
'__FILE__' => ['__FILE__'],
239+
'__FUNCTION__' => ['__FUNCTION__'],
240+
'__LINE__' => ['__LINE__'],
241+
'__METHOD__' => ['__METHOD__'],
242+
'__NAMESPACE__' => ['__NAMESPACE__'],
243+
'__TRAIT__' => ['__TRAIT__'],
244+
];
245+
}
246+
247+
/**
248+
* @dataProvider invalidClassNameExamples
51249
* @test
250+
* @param string $invalidClassName
52251
* @return void
53252
*/
54-
public function ensuresValidClassName(): void
253+
public function ensuresValidClassName(string $invalidClassName): void
55254
{
56-
// @TODO
255+
$this->expectExceptionMessageMatches('/invalid class name/i');
256+
257+
ClassName::fromString($invalidClassName);
57258
}
58259

59260
/**
@@ -94,4 +295,4 @@ public function providesShortClassName(): void
94295
$className->getShortClassName()
95296
);
96297
}
97-
}
298+
}

0 commit comments

Comments
 (0)