diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 2dca508..7d99d06 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
- php-version: [ '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ]
+ php-version: [ '8.1', '8.2', '8.3', '8.4', '8.5' ]
steps:
- name: Checkout
diff --git a/composer.json b/composer.json
index 83ac8ac..3f4d01f 100644
--- a/composer.json
+++ b/composer.json
@@ -13,15 +13,16 @@
"require": {
"php": ">=8.0",
"latte/latte": "^3.0",
- "nette/forms": "^3.0",
+ "nette/forms": "^3.1",
"nette/utils": "^3.0 || ^4.0"
},
"require-dev": {
"nette/application": "^3.0",
- "nette/bootstrap": "^3.0",
+ "nette/bootstrap": "^3.1",
"nette/di": "^3.0",
- "phpstan/phpstan": "~1.8",
- "phpstan/phpstan-nette": "~1.0",
+ "phpstan/phpstan": "^2.2",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-nette": "^2.0",
"tracy/tracy": "^2.5"
},
"extra": {
@@ -42,6 +43,10 @@
"psr-4": {
"NextrasDemos\\FormsRendering\\LatteMacros\\": "examples/lattemacros/",
"NextrasDemos\\FormsRendering\\Renderers\\": "examples/renderers/"
- }
+ },
+ "exclude-from-classmap": [
+ "examples/lattemacros/temp/",
+ "examples/renderers/temp/"
+ ]
}
}
diff --git a/examples/.gitignore b/examples/.gitignore
deleted file mode 100644
index c1e0f82..0000000
--- a/examples/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-log
-temp
diff --git a/examples/lattemacros/LatteMacrosPresenter.latte b/examples/lattemacros/LatteMacrosPresenter.latte
index 1c38888..5dbb203 100644
--- a/examples/lattemacros/LatteMacrosPresenter.latte
+++ b/examples/lattemacros/LatteMacrosPresenter.latte
@@ -12,7 +12,7 @@
Form LatteMacros rendering
- {form form class => form-horizontal}
+ {form form, class => form-horizontal}
{bsLabel text class => "label-control col-sm-3" /}
{bsInput text}{inputError text}
diff --git a/examples/lattemacros/index.php b/examples/lattemacros/index.php
index dadb9aa..1f90820 100644
--- a/examples/lattemacros/index.php
+++ b/examples/lattemacros/index.php
@@ -3,13 +3,13 @@
namespace NextrasDemos\FormsRendering\LatteMacros;
use Nette\Application\Application;
-use Nette\Configurator;
+use Nette\Bootstrap\Configurator;
require_once __DIR__ . '/../../vendor/autoload.php';
$configurator = new Configurator;
-$configurator->enableDebugger(__DIR__ . '/log');
+$configurator->enableTracy(__DIR__ . '/log');
$configurator->setTempDirectory(__DIR__ . '/temp');
$configurator->addConfig(__DIR__ . '/config.neon');
diff --git a/examples/lattemacros/log/.gitignore b/examples/lattemacros/log/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/examples/lattemacros/log/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/examples/lattemacros/temp/.gitignore b/examples/lattemacros/temp/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/examples/lattemacros/temp/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/examples/renderers/RenderersPresenter.php b/examples/renderers/RenderersPresenter.php
index 8a03322..a062a3c 100644
--- a/examples/renderers/RenderersPresenter.php
+++ b/examples/renderers/RenderersPresenter.php
@@ -2,6 +2,7 @@
namespace NextrasDemos\FormsRendering\Renderers;
+use Nette\Application\Attributes\Persistent;
use Nette\Application\UI\Form;
use Nette\Application\UI\Presenter;
use Nextras\FormsRendering\Renderers\Bs3FormRenderer;
@@ -14,14 +15,14 @@ class RenderersPresenter extends Presenter
{
/**
* @var string
- * @persistent
*/
+ #[Persistent]
public $renderer = 'bs3';
/**
* @var bool
- * @persistent
*/
+ #[Persistent]
public $showBulky = true;
diff --git a/examples/renderers/index.php b/examples/renderers/index.php
index 70c1d06..f748bc6 100644
--- a/examples/renderers/index.php
+++ b/examples/renderers/index.php
@@ -3,13 +3,13 @@
namespace NextrasDemos\FormsRendering\Renderers;
use Nette\Application\Application;
-use Nette\Configurator;
+use Nette\Bootstrap\Configurator;
require_once __DIR__ . '/../../vendor/autoload.php';
$configurator = new Configurator;
-$configurator->enableDebugger(__DIR__ . '/log');
+$configurator->enableTracy(__DIR__ . '/log');
$configurator->setTempDirectory(__DIR__ . '/temp');
$configurator->addConfig(__DIR__ . '/config.neon');
diff --git a/examples/renderers/log/.gitignore b/examples/renderers/log/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/examples/renderers/log/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/examples/renderers/temp/.gitignore b/examples/renderers/temp/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/examples/renderers/temp/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/phpstan.dist.neon b/phpstan.dist.neon
index ead69a6..f3f56a9 100644
--- a/phpstan.dist.neon
+++ b/phpstan.dist.neon
@@ -1,4 +1,5 @@
includes:
+ - vendor/phpstan/phpstan-deprecation-rules/rules.neon
- vendor/phpstan/phpstan-nette/extension.neon
- vendor/phpstan/phpstan-nette/rules.neon
@@ -7,3 +8,14 @@ parameters:
paths:
- src
- examples
+
+ excludePaths:
+ - examples/**/log/*
+ - examples/**/temp/*
+
+ ignoreErrors:
+ -
+ message: '#^Call to function method_exists\(\) with ''Nette\\\\Bridges\\\\FormsLatte\\\\Runtime'' and ''renderFormBegin'' will always evaluate to true\.$#'
+ identifier: function.alreadyNarrowedType
+ reportUnmatched: false
+ path: src/LatteTags/InputNode.php
diff --git a/src/LatteTags/Bs3/Bs3InputNode.php b/src/LatteTags/Bs3/Bs3InputNode.php
index 12dd16f..2627651 100644
--- a/src/LatteTags/Bs3/Bs3InputNode.php
+++ b/src/LatteTags/Bs3/Bs3InputNode.php
@@ -18,17 +18,19 @@
class Bs3InputNode extends BaseInputNode
{
+ private const INPUT_CONTROLS = ['radio', 'checkbox', 'file', 'hidden', 'range', 'image', 'submit', 'reset'];
+
public static function input(Html $input, BaseControl $control, bool $isSubItem): Html
{
- static $inputControls = ['radio', 'checkbox', 'file', 'hidden', 'range', 'image', 'submit', 'reset'];
$name = $input->getName();
if (
$name === 'select' ||
$name === 'textarea' ||
- ($name === 'input' && !in_array($input->type, $inputControls, true))
+ ($name === 'input' && !in_array($input->type, self::INPUT_CONTROLS, true))
) {
$input->addClass('form-control');
- } elseif ($name === 'input' && ($input->type === 'submit' || $input->type === 'reset')) {
+ } elseif ($name === 'input' && ($input->type === 'submit' || $input->type === 'reset') && is_string($input->value)) {
+ // is_string above is needed by PHPStan since phpstan-nette claims all properties are mixed.
$input->setName('button');
$input->addHtml($input->value);
$input->addClass('btn');
diff --git a/src/LatteTags/InputNode.php b/src/LatteTags/InputNode.php
index 93adbe7..3fa332c 100644
--- a/src/LatteTags/InputNode.php
+++ b/src/LatteTags/InputNode.php
@@ -9,7 +9,6 @@
namespace Nextras\FormsRendering\LatteTags;
-use Latte\Compiler\Nodes\Php\Scalar\StringNode;
use Latte\Compiler\PrintContext;
use Nette\Bridges\FormsLatte\Nodes\InputNode as NetteInputNode;
use Nette\Forms\Controls\BaseControl;
@@ -21,8 +20,10 @@ abstract class InputNode extends NetteInputNode
public function print(PrintContext $context): string
{
$class = static::class;
+ $hasForms33 = method_exists(\Nette\Bridges\FormsLatte\Runtime::class, 'renderFormBegin');
+ $input = $hasForms33 ? 'Nette\Bridges\FormsLatte\Runtime::item(%node, $this->global)' : '$this->global->forms->get(%node);';
return $context->format(
- '$ʟ_input = Nette\Bridges\FormsLatte\Runtime::item(%node, $this->global);'
+ '$ʟ_input = ' . $input
. 'echo ' . $class . '::input($ʟ_input->'
. ($this->part ? ('getControlPart(%node)') : 'getControl()')
. ($this->attributes->items ? '->addAttributes(%2.node)' : '')
diff --git a/src/LatteTags/LabelNode.php b/src/LatteTags/LabelNode.php
index 33590b5..b8b3810 100644
--- a/src/LatteTags/LabelNode.php
+++ b/src/LatteTags/LabelNode.php
@@ -16,6 +16,13 @@
use Nette\Utils\Html;
+/**
+ * These are fake properties to satisfy PHPStan, at least one of them
+ * should exist depending on the versions of installed dependencies.
+ * TODO: Drop these once we no longer support forms < 3.3.
+ * @property-read array
$tagRanges
+ * @property-read mixed $endLine
+ */
abstract class LabelNode extends NetteLabelNode
{
public function print(PrintContext $context): string
@@ -34,7 +41,7 @@ public function print(PrintContext $context): string
$this->position,
$this->content,
// TODO: Drop the `endLine` support once we no longer support forms < 3.3.
- property_exists($this, 'endLine') ? $this->endLine : end($this->tagRanges),
+ isset($this->endLine) ? $this->endLine : end($this->tagRanges),
);
}
diff --git a/src/Renderers/Bs3FormRenderer.php b/src/Renderers/Bs3FormRenderer.php
index fb382b1..4bda0ba 100644
--- a/src/Renderers/Bs3FormRenderer.php
+++ b/src/Renderers/Bs3FormRenderer.php
@@ -137,7 +137,11 @@ private function controlsInit(): void
if ($control instanceof Controls\Checkbox) {
$control->getContainerPrototype()->setName('div')->appendAttribute('class', $control->getControlPrototype()->type);
} else {
- $control->getItemLabelPrototype()->addClass($control->getControlPrototype()->type . '-inline');
+ $type = $control->getControlPrototype()->type;
+ // For PHPStan: Both control types initialize it to constructor in string.
+ \assert(is_string($type));
+
+ $control->getItemLabelPrototype()->addClass($type . '-inline');
}
}
}
diff --git a/src/Renderers/Bs4FormRenderer.php b/src/Renderers/Bs4FormRenderer.php
index 2397ed4..8afba02 100644
--- a/src/Renderers/Bs4FormRenderer.php
+++ b/src/Renderers/Bs4FormRenderer.php
@@ -148,7 +148,10 @@ private function controlsInit(): void
foreach ($this->form->getControls() as $control) {
if ($this->layout === FormLayout::INLINE && !$control instanceof Controls\Checkbox && method_exists($control, 'getLabelPrototype')) {
- $control->getLabelPrototype()->addClass('my-1')->addClass('mr-2');
+ $labelPrototype = $control->getLabelPrototype();
+ \assert($labelPrototype instanceof Html); // For PHPStan
+
+ $labelPrototype->addClass('my-1')->addClass('mr-2');
}
if ($control instanceof Controls\Button) {
@@ -167,7 +170,8 @@ private function controlsInit(): void
} elseif ($control instanceof Controls\Checkbox || $control instanceof Controls\CheckboxList || $control instanceof Controls\RadioList) {
$control->getControlPrototype()->addClass('form-check-input');
- $control->getSeparatorPrototype()
+ $wrapper = $control instanceof Controls\Checkbox ? $control->getContainerPrototype() : $control->getSeparatorPrototype();
+ $wrapper
->setName('div')
->appendAttribute('class', 'form-check')
->appendAttribute('class', 'form-check-inline', $this->layout == FormLayout::INLINE);
diff --git a/src/Renderers/Bs5FormRenderer.php b/src/Renderers/Bs5FormRenderer.php
index c920aef..2a232a7 100644
--- a/src/Renderers/Bs5FormRenderer.php
+++ b/src/Renderers/Bs5FormRenderer.php
@@ -158,20 +158,26 @@ private function controlsInit(): void
// The checkboxes are exception since they have their own inline class.
if (!$control instanceof Controls\Checkbox && !$control instanceof Controls\CheckboxList && !$control instanceof Controls\RadioList && method_exists($control, 'getControlPrototype')) {
- $control->getControlPrototype()->addClass('d-inline-block');
+ $controlPrototype = $control->getControlPrototype();
+ \assert($controlPrototype instanceof Html); // For PHPStan
+
+ $controlPrototype->addClass('d-inline-block');
// But setting `display: inline-block` is not enough since the widgets will inherit
// `width: 100%` from `.form-control` and end up wrapped anyway.
// Let’s counter that using `width: auto`.
- $control->getControlPrototype()->addClass('w-auto');
+ $controlPrototype->addClass('w-auto');
if ($control instanceof Controls\TextBase && $control->control->type === 'color') {
// `input[type=color]` is a special case since `width: auto` would make it squish.
- $control->getControlPrototype()->addStyle('min-width', '3rem');
+ $controlPrototype->addStyle('min-width', '3rem');
}
}
+ $labelPrototype = $control->getLabelPrototype();
+ \assert($labelPrototype instanceof Html); // For PHPStan
+
// Also, we need to add some spacing between the label and the control.
- $control->getLabelPrototype()->addClass('me-2');
+ $labelPrototype->addClass('me-2');
}
if ($control instanceof Controls\Button) {
@@ -204,7 +210,8 @@ private function controlsInit(): void
$control->getControlPrototype()->addClass('form-check-input');
// They also need to be individually wrapped in `div.form-check`.
- $control->getSeparatorPrototype()
+ $wrapper = $control instanceof Controls\Checkbox ? $control->getContainerPrototype() : $control->getSeparatorPrototype();
+ $wrapper
->setName('div')
->appendAttribute('class', 'form-check')
// They support being displayed inline with `.form-check-inline`.