Skip to content

Commit d36064d

Browse files
pierredupdg
authored andcommitted
ClassType, Method: added validate(), prevents classes and methods from declared both final and abstract [BC break] (#36)
1 parent 1ed1531 commit d36064d

File tree

9 files changed

+102
-12
lines changed

9 files changed

+102
-12
lines changed

readme.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ Usage is very easy. Let's start with a straightforward example of generating cla
4242
$class = new Nette\PhpGenerator\ClassType('Demo');
4343

4444
$class
45-
->setAbstract()
4645
->setFinal()
4746
->setExtends('ParentClass')
4847
->addImplement('Countable')
@@ -63,7 +62,7 @@ It will render this result:
6362
*
6463
* @property-read Nette\Forms\Form $form
6564
*/
66-
abstract final class Demo extends ParentClass implements Countable
65+
final class Demo extends ParentClass implements Countable
6766
{
6867
use Nette\SmartObject;
6968
}

src/PhpGenerator/ClassType.php

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public function setExtends($names): self
183183
if (!is_string($names) && !is_array($names)) {
184184
throw new Nette\InvalidArgumentException('Argument must be string or string[].');
185185
}
186-
$this->validate((array) $names);
186+
$this->validateNames((array) $names);
187187
$this->extends = $names;
188188
return $this;
189189
}
@@ -203,7 +203,7 @@ public function getExtends()
203203
*/
204204
public function addExtend(string $name): self
205205
{
206-
$this->validate([$name]);
206+
$this->validateNames([$name]);
207207
$this->extends = (array) $this->extends;
208208
$this->extends[] = $name;
209209
return $this;
@@ -216,7 +216,7 @@ public function addExtend(string $name): self
216216
*/
217217
public function setImplements(array $names): self
218218
{
219-
$this->validate($names);
219+
$this->validateNames($names);
220220
$this->implements = $names;
221221
return $this;
222222
}
@@ -236,7 +236,7 @@ public function getImplements(): array
236236
*/
237237
public function addImplement(string $name): self
238238
{
239-
$this->validate([$name]);
239+
$this->validateNames([$name]);
240240
$this->implements[] = $name;
241241
return $this;
242242
}
@@ -248,7 +248,7 @@ public function addImplement(string $name): self
248248
*/
249249
public function setTraits(array $names): self
250250
{
251-
$this->validate($names);
251+
$this->validateNames($names);
252252
$this->traits = array_fill_keys($names, []);
253253
return $this;
254254
}
@@ -277,7 +277,7 @@ public function getTraitResolutions(): array
277277
*/
278278
public function addTrait(string $name, array $resolutions = []): self
279279
{
280-
$this->validate([$name]);
280+
$this->validateNames([$name]);
281281
$this->traits[$name] = $resolutions;
282282
return $this;
283283
}
@@ -461,7 +461,21 @@ public function removeMethod(string $name): self
461461
}
462462

463463

464-
private function validate(array $names): void
464+
/**
465+
* @throws Nette\InvalidStateException
466+
*/
467+
public function validate(): void
468+
{
469+
if ($this->abstract && $this->final) {
470+
throw new Nette\InvalidStateException('Class cannot be abstract and final.');
471+
472+
} elseif (!$this->name && ($this->abstract || $this->final)) {
473+
throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.');
474+
}
475+
}
476+
477+
478+
private function validateNames(array $names): void
465479
{
466480
foreach ($names as $name) {
467481
if (!Helpers::isNamespaceIdentifier($name, true)) {

src/PhpGenerator/Method.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,15 @@ public function isAbstract(): bool
120120
{
121121
return $this->abstract;
122122
}
123+
124+
125+
/**
126+
* @throws Nette\InvalidStateException
127+
*/
128+
public function validate(): void
129+
{
130+
if ($this->abstract && ($this->final || $this->visibility === ClassType::VISIBILITY_PRIVATE)) {
131+
throw new Nette\InvalidStateException('Method cannot be abstract and final or private.');
132+
}
133+
}
123134
}

src/PhpGenerator/Printer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public function printClosure(Closure $closure): string
6060

6161
public function printMethod(Method $method, PhpNamespace $namespace = null): string
6262
{
63+
$method->validate();
6364
return Helpers::formatDocComment($method->getComment() . "\n")
6465
. ($method->isAbstract() ? 'abstract ' : '')
6566
. ($method->isFinal() ? 'final ' : '')
@@ -81,6 +82,7 @@ public function printMethod(Method $method, PhpNamespace $namespace = null): str
8182

8283
public function printClass(ClassType $class, PhpNamespace $namespace = null): string
8384
{
85+
$class->validate();
8486
$resolver = $namespace ? [$namespace, 'unresolveName'] : function ($s) { return $s; };
8587

8688
$traits = [];

tests/PhpGenerator/ClassType.phpt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ Assert::same([], $class->getTraitResolutions());
2424

2525
$class
2626
->setAbstract(true)
27-
->setFinal(true)
2827
->setExtends('ParentClass')
2928
->addImplement('IExample')
3029
->addImplement('IOne')
@@ -35,7 +34,7 @@ $class
3534
->setConstants(['ROLE' => 'admin'])
3635
->addConstant('ACTIVE', false);
3736

38-
Assert::true($class->isFinal());
37+
Assert::false($class->isFinal());
3938
Assert::true($class->isAbstract());
4039
Assert::same('ParentClass', $class->getExtends());
4140
Assert::same(['ObjectTrait', 'AnotherTrait'], $class->getTraits());
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\PhpGenerator\ClassType;
6+
use Tester\Assert;
7+
8+
9+
require __DIR__ . '/../bootstrap.php';
10+
11+
12+
Assert::exception(function () {
13+
$class = new ClassType('A');
14+
$class->setFinal(true)->setAbstract(true);
15+
$class->validate();
16+
}, Nette\InvalidStateException::class, 'Class cannot be abstract and final.');
17+
18+
Assert::exception(function () {
19+
$class = new ClassType('A');
20+
$class->setAbstract(true)->setFinal(true);
21+
$class->validate();
22+
}, Nette\InvalidStateException::class, 'Class cannot be abstract and final.');
23+
24+
Assert::exception(function () {
25+
$class = new ClassType;
26+
$class->setAbstract(true);
27+
$class->validate();
28+
}, Nette\InvalidStateException::class, 'Anonymous class cannot be abstract or final.');
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\PhpGenerator\Method;
6+
use Tester\Assert;
7+
8+
9+
require __DIR__ . '/../bootstrap.php';
10+
11+
12+
Assert::exception(function () {
13+
$method = new Method('foo');
14+
$method->setFinal(true)->setAbstract(true);
15+
$method->validate();
16+
}, Nette\InvalidStateException::class, 'Method cannot be abstract and final or private.');
17+
18+
Assert::exception(function () {
19+
$method = new Method('foo');
20+
$method->setAbstract(true)->setFinal(true);
21+
$method->validate();
22+
}, Nette\InvalidStateException::class, 'Method cannot be abstract and final or private.');
23+
24+
Assert::exception(function () {
25+
$method = new Method('foo');
26+
$method->setAbstract(true)->setVisibility('private');
27+
$method->validate();
28+
}, Nette\InvalidStateException::class, 'Method cannot be abstract and final or private.');

tests/PhpGenerator/Printer.phpt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ declare(strict_types=1);
55
use Nette\PhpGenerator\ClassType;
66
use Nette\PhpGenerator\PhpLiteral;
77
use Nette\PhpGenerator\Printer;
8+
use Tester\Assert;
89

910

1011
require __DIR__ . '/../bootstrap.php';
@@ -65,3 +66,11 @@ $closure
6566
->setTypeHint('stdClass');
6667

6768
sameFile(__DIR__ . '/expected/Printer.closure.expect', $printer->printClosure($closure));
69+
70+
71+
// printer validates class
72+
Assert::exception(function () {
73+
$class = new ClassType;
74+
$class->setFinal(true)->setAbstract(true);
75+
(new Printer)->printClass($class);
76+
}, Nette\InvalidStateException::class, 'Class cannot be abstract and final.');

tests/PhpGenerator/expected/ClassType.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*
55
* @property-read Nette\Forms\Form $form
66
*/
7-
abstract final class Example extends ParentClass implements IExample, IOne
7+
abstract class Example extends ParentClass implements IExample, IOne
88
{
99
use ObjectTrait;
1010
use AnotherTrait {

0 commit comments

Comments
 (0)