Skip to content

Commit

Permalink
ClassType, Method: added validate(), prevents classes and methods fro…
Browse files Browse the repository at this point in the history
…m declared both final and abstract [BC break] (#36)
  • Loading branch information
pierredup authored and dg committed Aug 29, 2018
1 parent 68789de commit 2b44941
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 12 deletions.
3 changes: 1 addition & 2 deletions readme.md
Expand Up @@ -42,7 +42,6 @@ Usage is very easy. Let's start with a straightforward example of generating cla
$class = new Nette\PhpGenerator\ClassType('Demo');

$class
->setAbstract()
->setFinal()
->setExtends('ParentClass')
->addImplement('Countable')
Expand All @@ -63,7 +62,7 @@ It will render this result:
*
* @property-read Nette\Forms\Form $form
*/
abstract final class Demo extends ParentClass implements Countable
final class Demo extends ParentClass implements Countable
{
use Nette\SmartObject;
}
Expand Down
25 changes: 18 additions & 7 deletions src/PhpGenerator/ClassType.php
Expand Up @@ -183,7 +183,7 @@ public function setExtends($names): self
if (!is_string($names) && !is_array($names)) {
throw new Nette\InvalidArgumentException('Argument must be string or string[].');
}
$this->validate((array) $names);
$this->validateNames((array) $names);
$this->extends = $names;
return $this;
}
Expand All @@ -203,7 +203,7 @@ public function getExtends()
*/
public function addExtend(string $name): self
{
$this->validate([$name]);
$this->validateNames([$name]);
$this->extends = (array) $this->extends;
$this->extends[] = $name;
return $this;
Expand All @@ -216,7 +216,7 @@ public function addExtend(string $name): self
*/
public function setImplements(array $names): self
{
$this->validate($names);
$this->validateNames($names);
$this->implements = $names;
return $this;
}
Expand All @@ -236,7 +236,7 @@ public function getImplements(): array
*/
public function addImplement(string $name): self
{
$this->validate([$name]);
$this->validateNames([$name]);
$this->implements[] = $name;
return $this;
}
Expand All @@ -248,7 +248,7 @@ public function addImplement(string $name): self
*/
public function setTraits(array $names): self
{
$this->validate($names);
$this->validateNames($names);
$this->traits = array_fill_keys($names, []);
return $this;
}
Expand Down Expand Up @@ -277,7 +277,7 @@ public function getTraitResolutions(): array
*/
public function addTrait(string $name, array $resolutions = []): self
{
$this->validate([$name]);
$this->validateNames([$name]);
$this->traits[$name] = $resolutions;
return $this;
}
Expand Down Expand Up @@ -461,7 +461,18 @@ public function removeMethod(string $name): self
}


private function validate(array $names): void
/**
* @throws Nette\InvalidStateException
*/
public function validate(): void
{
if ($this->abstract && $this->final) {
throw new Nette\InvalidStateException('Class cannot be abstract and final.');
}
}


private function validateNames(array $names): void
{
foreach ($names as $name) {
if (!Helpers::isNamespaceIdentifier($name, true)) {
Expand Down
11 changes: 11 additions & 0 deletions src/PhpGenerator/Method.php
Expand Up @@ -129,4 +129,15 @@ public function isAbstract(): bool
{
return $this->abstract;
}


/**
* @throws Nette\InvalidStateException
*/
public function validate(): void
{
if ($this->abstract && ($this->final || $this->visibility === ClassType::VISIBILITY_PRIVATE)) {
throw new Nette\InvalidStateException('Method cannot be abstract and final or private.');
}
}
}
2 changes: 2 additions & 0 deletions src/PhpGenerator/Printer.php
Expand Up @@ -60,6 +60,7 @@ public function printClosure(Closure $closure): string

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

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

$traits = [];
Expand Down
3 changes: 1 addition & 2 deletions tests/PhpGenerator/ClassType.phpt
Expand Up @@ -24,7 +24,6 @@ Assert::same([], $class->getTraitResolutions());

$class
->setAbstract(true)
->setFinal(true)
->setExtends('ParentClass')
->addImplement('IExample')
->addImplement('IOne')
Expand All @@ -35,7 +34,7 @@ $class
->setConstants(['ROLE' => 'admin'])
->addConstant('ACTIVE', false);

Assert::true($class->isFinal());
Assert::false($class->isFinal());
Assert::true($class->isAbstract());
Assert::same('ParentClass', $class->getExtends());
Assert::same(['ObjectTrait', 'AnotherTrait'], $class->getTraits());
Expand Down
23 changes: 23 additions & 0 deletions tests/PhpGenerator/ClassType.validate.phpt
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

use Nette\PhpGenerator\ClassType;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


// class cannot be final and abstract
Assert::exception(function () {
$class = new ClassType;
$class->setFinal(true)->setAbstract(true);
$class->validate();
}, Nette\InvalidStateException::class, 'Class cannot be abstract and final.');

Assert::exception(function () {
$class = new ClassType;
$class->setAbstract(true)->setFinal(true);
$class->validate();
}, Nette\InvalidStateException::class, 'Class cannot be abstract and final.');
29 changes: 29 additions & 0 deletions tests/PhpGenerator/Method.validate.phpt
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

use Nette\PhpGenerator\Method;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


// method cannot be final and abstract
Assert::exception(function () {
$method = new Method('foo');
$method->setFinal(true)->setAbstract(true);
$method->validate();
}, Nette\InvalidStateException::class, 'Method cannot be abstract and final or private.');

Assert::exception(function () {
$method = new Method('foo');
$method->setAbstract(true)->setFinal(true);
$method->validate();
}, Nette\InvalidStateException::class, 'Method cannot be abstract and final or private.');

Assert::exception(function () {
$method = new Method('foo');
$method->setAbstract(true)->setVisibility('private');
$method->validate();
}, Nette\InvalidStateException::class, 'Method cannot be abstract and final or private.');
9 changes: 9 additions & 0 deletions tests/PhpGenerator/Printer.phpt
Expand Up @@ -5,6 +5,7 @@ declare(strict_types=1);
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpLiteral;
use Nette\PhpGenerator\Printer;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';
Expand Down Expand Up @@ -65,3 +66,11 @@ $closure
->setTypeHint('stdClass');

sameFile(__DIR__ . '/expected/Printer.closure.expect', $printer->printClosure($closure));


// printer validates class
Assert::exception(function () {
$class = new ClassType;
$class->setFinal(true)->setAbstract(true);
(new Printer)->printClass($class);
}, Nette\InvalidStateException::class, 'Class cannot be abstract and final.');
2 changes: 1 addition & 1 deletion tests/PhpGenerator/expected/ClassType.expect
Expand Up @@ -4,7 +4,7 @@
*
* @property-read Nette\Forms\Form $form
*/
abstract final class Example extends ParentClass implements IExample, IOne
abstract class Example extends ParentClass implements IExample, IOne
{
use ObjectTrait;
use AnotherTrait {
Expand Down

0 comments on commit 2b44941

Please sign in to comment.