Skip to content

Commit 1e86896

Browse files
committed
ApplicationExtension: checks the correct usage of attributes
1 parent ca9fbae commit 1e86896

1 file changed

Lines changed: 41 additions & 0 deletions

File tree

src/Bridges/ApplicationDI/ApplicationExtension.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
use Composer\Autoload\ClassLoader;
1313
use Nette;
14+
use Nette\Application\Attributes;
1415
use Nette\Application\UI;
1516
use Nette\DI\Definitions;
1617
use Nette\Schema\Expect;
18+
use Nette\Utils\Reflection;
1719
use Tracy;
1820

1921

@@ -24,6 +26,7 @@ final class ApplicationExtension extends Nette\DI\CompilerExtension
2426
{
2527
private readonly array $scanDirs;
2628
private int $invalidLinkMode;
29+
private array $checked = [];
2730

2831

2932
public function __construct(
@@ -135,6 +138,7 @@ public function beforeCompile(): void
135138

136139
$counter = 0;
137140
foreach ($this->findPresenters() as $class) {
141+
$this->checkPresenter($class);
138142
if (empty($all[$class])) {
139143
$all[$class] = $builder->addDefinition($this->prefix((string) ++$counter))
140144
->setType($class);
@@ -247,4 +251,41 @@ public static function generateNewPresenterFileContents(string $file, ?string $c
247251

248252
return $res . "use Nette;\n\n\nclass $class extends Nette\\Application\\UI\\Presenter\n{\n\$END\$\n}\n";
249253
}
254+
255+
256+
private function checkPresenter(string $class): void
257+
{
258+
if (!is_subclass_of($class, UI\Presenter::class) || isset($this->checked[$class])) {
259+
return;
260+
}
261+
$this->checked[$class] = true;
262+
263+
$rc = new \ReflectionClass($class);
264+
if ($rc->getParentClass()) {
265+
$this->checkPresenter($rc->getParentClass()->getName());
266+
}
267+
268+
foreach ($rc->getProperties() as $rp) {
269+
if (($rp->getAttributes($attr = Attributes\Parameter::class) || $rp->getAttributes($attr = Attributes\Persistent::class))
270+
&& (!$rp->isPublic() || $rp->isStatic() || $rp->isReadOnly())
271+
) {
272+
throw new Nette\InvalidStateException(sprintf('Property %s: attribute %s can be used only with public non-static property.', Reflection::toString($rp), $attr));
273+
}
274+
}
275+
276+
$re = $class::formatActionMethod('') . '.|' . $class::formatRenderMethod('') . '.|' . $class::formatSignalMethod('') . '.';
277+
foreach ($rc->getMethods() as $rm) {
278+
if (preg_match("#^(?!handleInvalidLink)($re)#", $rm->getName()) && (!$rm->isPublic() || $rm->isStatic())) {
279+
throw new Nette\InvalidStateException(sprintf('Method %s: this method must be public non-static.', Reflection::toString($rm)));
280+
} elseif (preg_match('#^createComponent.#', $rm->getName()) && ($rm->isPrivate() || $rm->isStatic())) {
281+
throw new Nette\InvalidStateException(sprintf('Method %s: this method must be non-private non-static.', Reflection::toString($rm)));
282+
} elseif ($rm->getAttributes(Attributes\Requires::class, \ReflectionAttribute::IS_INSTANCEOF)
283+
&& !preg_match("#^$re|createComponent.#", $rm->getName())
284+
) {
285+
throw new Nette\InvalidStateException(sprintf('Method %s: attribute %s can be used only with action, render, handle or createComponent methods.', Reflection::toString($rm), Attributes\Requires::class));
286+
} elseif ($rm->getAttributes(Attributes\Deprecated::class) && !preg_match("#^$re#", $rm->getName())) {
287+
throw new Nette\InvalidStateException(sprintf('Method %s: attribute %s can be used only with action, render or handle methods.', Reflection::toString($rm), Attributes\Deprecated::class));
288+
}
289+
}
290+
}
250291
}

0 commit comments

Comments
 (0)