diff --git a/.gitignore b/.gitignore
index c253173..eb49355 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,7 @@ composer.lock
vendor/
test.php
+
+.phpunit.result.cache
+
+.php-cs-fixer.cache
diff --git a/composer.json b/composer.json
index d1020e7..f760e57 100644
--- a/composer.json
+++ b/composer.json
@@ -1,31 +1,66 @@
{
- "name": "kgrzelak/laravel-form",
- "license": "MIT",
- "authors": [
- {
- "name": "Krzysztof Grzelak",
- "email": "krzysztof@grzelak.dev",
- "homepage": "https://grzelak.dev"
+ "name": "kgrzelak/laravel-form",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Krzysztof Grzelak",
+ "email": "krzysztof@grzelak.dev",
+ "homepage": "https://grzelak.dev"
+ }
+ ],
+ "require": {
+ "php": "^8.2",
+ "spatie/laravel-package-tools": "^1.16.1",
+ "laravel/framework": "^10.41|^11.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.2",
+ "mockery/mockery": "^1.6",
+ "orchestra/testbench": "^9.1",
+ "friendsofphp/php-cs-fixer": "^3.59",
+ "phpstan/phpstan": "^1.11"
+ },
+ "autoload": {
+ "psr-4": {
+ "Kgrzelak\\LaravelForm\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Kgrzelak\\LaravelForm\\Tests\\": "tests",
+ "Workbench\\App\\": "workbench/app/",
+ "Workbench\\Database\\Factories\\": "workbench/database/factories/",
+ "Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
+ }
+ },
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Kgrzelak\\LaravelForm\\LaravelFormServiceProvider"
+ ],
+ "aliases": {
+ "Form": "Kgrzelak\\LaravelForm\\LaravelForm"
+ }
+ }
+ },
+ "scripts": {
+ "post-autoload-dump": [
+ "@clear",
+ "@prepare"
+ ],
+ "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
+ "prepare": "@php vendor/bin/testbench package:discover --ansi",
+ "build": "@php vendor/bin/testbench workbench:build --ansi",
+ "serve": [
+ "Composer\\Config::disableProcessTimeout",
+ "@build",
+ "@php vendor/bin/testbench serve"
+ ],
+ "lint": [
+ "@php vendor/bin/phpstan analyse"
+ ],
+ "test": [
+ "@php vendor/bin/phpunit"
+ ]
}
- ],
- "require": {
- "php": "^8.2",
- "spatie/laravel-package-tools": "^1.16.1",
- "laravel/framework": "^10.41|^11.0"
- },
- "autoload": {
- "psr-4": {
- "Kgrzelak\\LaravelForm\\": "src"
- }
- },
- "extra": {
- "laravel": {
- "providers": [
- "Kgrzelak\\LaravelForm\\LaravelFormServiceProvider"
- ],
- "aliases": {
- "Form": "Kgrzelak\\LaravelForm\\LaravelForm"
- }
- }
- }
}
diff --git a/config/laravel-form.php b/config/laravel-form.php
index 8ac3ea8..8099212 100644
--- a/config/laravel-form.php
+++ b/config/laravel-form.php
@@ -1,13 +1,21 @@
[
'class' => 'form-control'
],
'select' => [
'class' => 'form-select'
- ]
+ ],
-];
\ No newline at end of file
+ 'textarea' => [
+ 'class' => 'form-control'
+ ],
+
+ 'errors' => [
+ 'enabled' => true,
+ 'element-class' => 'is-invalid',
+ 'html' => ':message:'
+ ]
+];
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..6b3835b
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ ./tests
+
+
+
diff --git a/resources/views/_attributes.blade.php b/resources/views/_attributes.blade.php
deleted file mode 100644
index b681226..0000000
--- a/resources/views/_attributes.blade.php
+++ /dev/null
@@ -1,3 +0,0 @@
-@foreach($attributes as $key => $value)
-{{ $key }}="{{ $value }}"
-@endforeach
\ No newline at end of file
diff --git a/resources/views/input.blade.php b/resources/views/input.blade.php
deleted file mode 100644
index ed2e2aa..0000000
--- a/resources/views/input.blade.php
+++ /dev/null
@@ -1,9 +0,0 @@
-
-@error($name)
-
- {{ $message }}
-
-@enderror
diff --git a/resources/views/radio.blade.php b/resources/views/radio.blade.php
deleted file mode 100644
index 2049bd3..0000000
--- a/resources/views/radio.blade.php
+++ /dev/null
@@ -1,9 +0,0 @@
-
-@error($name)
-
- {{ $message }}
-
-@enderror
\ No newline at end of file
diff --git a/resources/views/select.blade.php b/resources/views/select.blade.php
deleted file mode 100644
index b7b93d0..0000000
--- a/resources/views/select.blade.php
+++ /dev/null
@@ -1,10 +0,0 @@
-
-@error($name)
-
- {{ $message }}
-
-@enderror
\ No newline at end of file
diff --git a/resources/views/textarea.blade.php b/resources/views/textarea.blade.php
deleted file mode 100644
index 3b686e1..0000000
--- a/resources/views/textarea.blade.php
+++ /dev/null
@@ -1,6 +0,0 @@
-
-@error($name)
-
- {{ $message }}
-
-@enderror
\ No newline at end of file
diff --git a/src/Attributes.php b/src/Attributes.php
new file mode 100644
index 0000000..e253ced
--- /dev/null
+++ b/src/Attributes.php
@@ -0,0 +1,104 @@
+attributes[$name]);
+ return $this;
+ }
+
+ $this->attributes[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * @description Get all attributes
+ * @return array
+ */
+ public function getAttributes(): array
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * @description Get attribute by name
+ * @param string $name
+ * @return string|null
+ */
+ public function getAttribute(string $name): ?string
+ {
+ return $this->attributes[$name] ?? null;
+ }
+
+ /**
+ * @description Add class to the attributes
+ * @param string $class
+ * @return Attributes
+ */
+ public function addClass(string $class): static
+ {
+ $this->classes[] = $class;
+
+ return $this;
+ }
+
+ /**
+ * @description Set class to the attributes
+ * @param string $class
+ * @return static
+ */
+ public function setClass(string $class): static
+ {
+ $this->classes = [$class];
+
+ return $this;
+ }
+
+ /**
+ * @description Get all classes
+ * @return array
+ */
+ public function getClass(): array
+ {
+ return $this->classes;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString(): string
+ {
+ $html = [];
+
+ if ($this->classes) {
+ $this->attributes['class'] = implode(' ', $this->classes);
+ }
+
+ foreach ($this->attributes as $name => $value) {
+ $html[$name] = sprintf('%s="%s"', $name, e($value));
+ }
+
+ return implode(' ', $html);
+ }
+}
diff --git a/src/BaseItem.php b/src/BaseItem.php
new file mode 100644
index 0000000..90895be
--- /dev/null
+++ b/src/BaseItem.php
@@ -0,0 +1,250 @@
+attributes = new Attributes();
+
+ $this->attributes->addClass(config('laravel-form.' . $this->viewName . '.class', 'form-control'));
+ $this->attributes->setAttribute('type', $this->type);
+ }
+
+ /**
+ * @description Set the id attribute of the input
+ * @param string|int $id
+ * @return static
+ */
+ public function id(string|int $id): static
+ {
+ $this->attributes->setAttribute('id', $id);
+
+ return $this;
+ }
+
+ /**
+ * @deprecated Use id() instead
+ * @param string $id
+ * @return static
+ */
+ public function setId(string $id): static
+ {
+ $this->attributes->setAttribute('id', $id);
+
+ return $this;
+ }
+
+ /**
+ * @description Set the name attribute of the input
+ * @param string $name - Name of the input
+ * @return static
+ */
+ public function name(string $name): static
+ {
+ $this->attributes->setAttribute('name', $name);
+
+ return $this;
+ }
+
+ /**
+ * @deprecated Use name() instead
+ * @param string $name - Name of the input
+ * @return $this
+ */
+ public function setName(string $name): static
+ {
+ $this->attributes->setAttribute('name', $name);
+
+ return $this;
+ }
+
+ public function type(string $type): static
+ {
+ $this->attributes->setAttribute('type', $type);
+
+ return $this;
+ }
+
+ /**
+ * @description Set the value of the input
+ * @param mixed|null $value - Value of the input
+ * @return static
+ */
+ public function value(mixed $value = null): static
+ {
+ if ($value instanceof BackedEnum) {
+ $value = $value->value;
+ }
+
+ if (app('request')->old($this->attributes->getAttribute('name'))) {
+ $value = app('request')->old($this->attributes->getAttribute('name'));
+ }
+
+ $this->attributes->setAttribute('value', $value);
+
+ return $this;
+ }
+
+ /**
+ * @deprecated Use value() instead
+ * @param mixed|null $value
+ * @return $this
+ */
+ public function setValue(mixed $value = null): static
+ {
+ if ($value instanceof BackedEnum) {
+ $value = $value->value;
+ }
+
+ if (app('request')->old($this->attributes->getAttribute('name'))) {
+ $value = app('request')->old($this->attributes->getAttribute('name'));
+ }
+
+ $this->attributes->setAttribute('value', $value);
+
+ return $this;
+ }
+
+ /**
+ * @description Set the placeholder of the input
+ * @param string $placeholder
+ * @return static
+ */
+ public function placeholder(string $placeholder): static
+ {
+ $this->attributes->setAttribute('placeholder', $placeholder);
+
+ return $this;
+ }
+
+ /**
+ * @description Set the required attribute of the input
+ * @param bool $required
+ * @return static
+ */
+ public function required(bool $required): static
+ {
+ $this->attributes->setAttribute('required', $required);
+
+ return $this;
+ }
+
+ /**
+ * @description Set the class of the input
+ * @param string $class - Name of the class
+ * @return static
+ */
+ public function setClass(string $class): static
+ {
+ $this->attributes->setClass($class);
+
+ return $this;
+ }
+
+ /**
+ * @description Add a class to the input
+ * @param string $class - Name of the class
+ * @return static
+ */
+ public function addClass(string $class): static
+ {
+ $this->attributes->addClass($class);
+
+ return $this;
+ }
+
+ /**
+ * @description Set the attribute of the input
+ * @param string|int $name - Name of the attribute
+ * @param string|null $value - Value of the attribute
+ * @return static
+ */
+ public function attribute(string|int $name, ?string $value = null): static
+ {
+ $this->attributes->setAttribute($name, $value);
+
+ return $this;
+ }
+
+ public function toHtml(): string
+ {
+ if ($this->hasError() && config('laravel-form.errors.enabled', false)) {
+ $this->attributes->addClass(config('laravel-form.errors.element-class', 'is-invalid'));
+ }
+
+ return new HtmlString(
+ match ($this->viewName) {
+ 'input' => (new InputRender())->render($this->attributes),
+ 'textarea' => (new TextareaRender())->render($this->attributes),
+ 'select' => (new SelectRender($this->options))->render($this->attributes),
+ } . $this->getErrors()
+ );
+ }
+
+ public function __toString(): string
+ {
+ return $this->toHtml();
+ }
+
+ private function getError(): ?string
+ {
+ if (!$this->hasError()) {
+ return null;
+ }
+
+ $errors = app('session')->get('errors', app(ViewErrorBag::class));
+
+ return $errors->getBag('default')->first($this->attributes->getAttribute('name'));
+ }
+
+ private function hasError()
+ {
+ if (!$name = $this->attributes->getAttribute('name')) {
+ return null;
+ }
+
+ $errors = app('session')->get('errors', app(ViewErrorBag::class));
+ if (!$errors->getBag('default')->has($name)) {
+ return null;
+ }
+
+ return $errors->getBag('default')->has($name);
+ }
+
+ private function getErrors(): string
+ {
+ $errors = '';
+ if (config('laravel-form.errors.enabled', false)) {
+ if ($error = $this->getError()) {
+ $errors =
+ Str::replace(
+ search: ':message:',
+ replace: $error,
+ subject: config('laravel-form.errors.html', ':message:')
+ );
+ }
+ }
+
+ return $errors;
+ }
+}
diff --git a/src/Items/Attributes.php b/src/Items/Attributes.php
deleted file mode 100644
index 6f00aa7..0000000
--- a/src/Items/Attributes.php
+++ /dev/null
@@ -1,26 +0,0 @@
- $this->attributes
- ])->render();
- }
-
- public function __toString()
- {
- return $this->toHtml();
- }
-}
\ No newline at end of file
diff --git a/src/Items/BaseItem.php b/src/Items/BaseItem.php
deleted file mode 100644
index ec11665..0000000
--- a/src/Items/BaseItem.php
+++ /dev/null
@@ -1,109 +0,0 @@
-attributes['id'] = $id;
-
- return $this;
- }
-
- public function setName(string $name): static
- {
- $this->name = $name;
-
- return $this;
- }
-
- public function setType(string $type): static
- {
- $this->type = $type;
-
- return $this;
- }
-
- public function setValue(mixed $value = null): static
- {
- if ($value instanceof BackedEnum) {
- $value = $value->value;
- }
-
- $this->value = $value;
-
- return $this;
- }
-
- public function setPlaceholder(string $placeholder): static
- {
- $this->placeholder = $placeholder;
-
- return $this;
- }
-
- public function setAttributes(array $attributes): static
- {
- $this->attributes = $attributes;
-
- return $this;
- }
-
- public function setRequired(bool $required): static
- {
- $this->required = $required;
-
- return $this;
- }
-
- public function setClass(string $class): static
- {
- $this->class = $class;
-
- return $this;
- }
-
- public function toHtml(): string
- {
- return view('laravel-form::' . $this->viewName, [
- 'name' => $this->name,
- 'class' => $this->class,
- 'type' => $this->type,
- 'value' => $this->value,
- 'placeholder' => $this->placeholder,
- 'attributes' => new Attributes($this->attributes),
- 'required' => $this->required,
- 'options' => $this->options
- ])->render();
- }
-
- public function __toString(): string
- {
- return $this->toHtml();
- }
-}
diff --git a/src/Items/FormInput.php b/src/Items/FormInput.php
index 2bab771..91aa1d3 100644
--- a/src/Items/FormInput.php
+++ b/src/Items/FormInput.php
@@ -2,10 +2,16 @@
namespace Kgrzelak\LaravelForm\Items;
+use Kgrzelak\LaravelForm\BaseItem;
+
class FormInput extends BaseItem
{
- public function __construct()
+ public string $viewName = 'input';
+
+ public ?string $type = 'text';
+
+ public function render(): string
{
- $this->class = config('laravel-form.input.class', 'form-input');
+ return 'attributes . '>';
}
-}
\ No newline at end of file
+}
diff --git a/src/Items/FormSelect.php b/src/Items/FormSelect.php
index 88456a3..49a2dee 100644
--- a/src/Items/FormSelect.php
+++ b/src/Items/FormSelect.php
@@ -2,13 +2,13 @@
namespace Kgrzelak\LaravelForm\Items;
+use Kgrzelak\LaravelForm\BaseItem;
+
class FormSelect extends BaseItem
{
- public function __construct()
- {
- $this->class = config('laravel-form.select.class', 'form-select');
- $this->viewName = 'select';
- }
+ public string $viewName = 'select';
+
+ protected array $options = [];
public function setOptions(array $options): static
{
@@ -23,4 +23,4 @@ public function addOption(string $value, string $label): static
return $this;
}
-}
\ No newline at end of file
+}
diff --git a/src/Items/FormTextarea.php b/src/Items/FormTextarea.php
index 59081a1..6d726ac 100644
--- a/src/Items/FormTextarea.php
+++ b/src/Items/FormTextarea.php
@@ -2,11 +2,9 @@
namespace Kgrzelak\LaravelForm\Items;
+use Kgrzelak\LaravelForm\BaseItem;
+
class FormTextarea extends BaseItem
{
- public function __construct ()
- {
- $this->class = config('laravel-form.textarea.class', 'form-control');
- $this->viewName = 'textarea';
- }
-}
\ No newline at end of file
+ public string $viewName = 'textarea';
+}
diff --git a/src/LaravelForm.php b/src/LaravelForm.php
index ce2098b..73408ea 100644
--- a/src/LaravelForm.php
+++ b/src/LaravelForm.php
@@ -22,4 +22,4 @@ public static function textarea(): FormTextarea
{
return new FormTextarea();
}
-}
\ No newline at end of file
+}
diff --git a/src/LaravelFormServiceProvider.php b/src/LaravelFormServiceProvider.php
index 91d63df..2839da8 100644
--- a/src/LaravelFormServiceProvider.php
+++ b/src/LaravelFormServiceProvider.php
@@ -13,4 +13,4 @@ public function configurePackage(Package $package): void
->hasConfigFile('laravel-form')
->hasViews('laravel-form');
}
-}
\ No newline at end of file
+}
diff --git a/src/Renders/InputRender.php b/src/Renders/InputRender.php
new file mode 100644
index 0000000..8ecc1db
--- /dev/null
+++ b/src/Renders/InputRender.php
@@ -0,0 +1,13 @@
+';
+ }
+}
diff --git a/src/Renders/Render.php b/src/Renders/Render.php
new file mode 100644
index 0000000..9236778
--- /dev/null
+++ b/src/Renders/Render.php
@@ -0,0 +1,10 @@
+getAttribute('value');
+ $attributes->setAttribute('value');
+
+ $options = '';
+
+ return $options;
+ }
+}
diff --git a/src/Renders/TextareaRender.php b/src/Renders/TextareaRender.php
new file mode 100644
index 0000000..ae53ae6
--- /dev/null
+++ b/src/Renders/TextareaRender.php
@@ -0,0 +1,16 @@
+getAttribute('value');
+ $attributes->setAttribute('value', null);
+
+ return '';
+ }
+}
diff --git a/tests/AttributesTest.php b/tests/AttributesTest.php
new file mode 100644
index 0000000..9f5a0ce
--- /dev/null
+++ b/tests/AttributesTest.php
@@ -0,0 +1,50 @@
+setClass('test-class');
+
+ $this->assertEquals(['test-class'], $attributes->getClass());
+ }
+
+ public function testCanAddClassAttribute(): void
+ {
+ $attributes = new Attributes();
+ $attributes->setClass('test-class');
+ $attributes->addClass('another-class');
+
+ $this->assertEquals(['test-class', 'another-class'], $attributes->getClass());
+ }
+
+ public function testCanAddAttribute(): void
+ {
+ $attributes = new Attributes();
+ $attributes->setAttribute('data-test', 'test');
+
+ $this->assertEquals(['data-test' => 'test'], $attributes->getAttributes());
+ }
+
+ public function testCanGetAttribute(): void
+ {
+ $attributes = new Attributes();
+ $attributes->setAttribute('data-test', 'test');
+
+ $this->assertEquals('test', $attributes->getAttribute('data-test'));
+ }
+
+ public function testCanRemoveAttribute(): void
+ {
+ $attributes = new Attributes();
+ $attributes->setAttribute('data-test', 'test');
+ $attributes->setAttribute('data-test');
+
+ $this->assertEquals([], $attributes->getAttributes());
+ }
+}
diff --git a/tests/FormInputTest.php b/tests/FormInputTest.php
new file mode 100644
index 0000000..cb9d455
--- /dev/null
+++ b/tests/FormInputTest.php
@@ -0,0 +1,85 @@
+assertEquals('', $item);
+ }
+
+ public function testCanCreateInputWithName(): void
+ {
+ $item = LaravelForm::input()->name('test');
+
+ $this->assertEquals('', $item);
+ }
+
+ public function testCanCreateInputWithId(): void
+ {
+ $item = LaravelForm::input()->id('test');
+
+ $this->assertEquals('', $item);
+ }
+
+ public function testCanCreateInputWithErrors(): void
+ {
+ $this->setErrors();
+
+ $item = LaravelForm::input()->name('test');
+
+ $this->assertEquals(
+ expected: 'error',
+ actual: $item
+ );
+ }
+
+ public function testCanCreateInputWithNameAndId(): void
+ {
+ $item = LaravelForm::input()->name('test')->id('test');
+
+ $this->assertEquals('', $item);
+ }
+
+ public function testCanCreateInputWithCustomAttribute(): void
+ {
+ $item = LaravelForm::input()->name('test')->attribute('data-test', 'test');
+
+ $this->assertEquals('', $item);
+ }
+
+ public function testCanReplaceClassAttribute(): void
+ {
+ $item = LaravelForm::input()->name('test')->setClass('test');
+
+ $this->assertEquals('', $item);
+ }
+
+ public function testErrorWorksWithCustomClassName(): void
+ {
+ $this->setErrors();
+
+ $item = LaravelForm::input()->name('test')->setClass('test');
+
+ $this->assertEquals(
+ expected: 'error',
+ actual: $item
+ );
+ }
+
+ public function testDontShowErrorsWhenDisabledInConfiguration(): void
+ {
+ $this->app['config']->set('laravel-form.errors.enabled', false);
+
+ $this->setErrors();
+
+ $item = LaravelForm::input()->name('test');
+
+ $this->assertEquals('', $item);
+ }
+}
diff --git a/tests/FormSelectTest.php b/tests/FormSelectTest.php
new file mode 100644
index 0000000..3060883
--- /dev/null
+++ b/tests/FormSelectTest.php
@@ -0,0 +1,37 @@
+name('test');
+
+ $this->assertEquals('', $select);
+ }
+
+ public function testCanAddOptionToSelect(): void
+ {
+ $select = LaravelForm::select()->name('test')->addOption('test', 'Test');
+
+ $this->assertEquals('', $select);
+ }
+
+ public function testCanAddMultipleOptionsToSelect(): void
+ {
+ $select = LaravelForm::select()->name('test')->addOption('test', 'Test')->addOption('test2', 'Test 2');
+
+ $this->assertEquals('', $select);
+ }
+
+ public function testSelectedOptionWorks()
+ {
+ $select = LaravelForm::select()->name('test')->addOption('test', 'Test')
+ ->addOption('test2', 'Test 2')->value('test2')->toHtml();
+
+ $this->assertEquals('', $select);
+ }
+}
diff --git a/tests/FormTextareaTest.php b/tests/FormTextareaTest.php
new file mode 100644
index 0000000..bfdd8df
--- /dev/null
+++ b/tests/FormTextareaTest.php
@@ -0,0 +1,62 @@
+assertEquals('', $item);
+ }
+
+ public function testCanCreateTextareaWithName(): void
+ {
+ $item = LaravelForm::textarea()->name('test');
+
+ $this->assertEquals('', $item);
+ }
+
+ public function testCanCreateTextareaWithId(): void
+ {
+ $item = LaravelForm::textarea()->id('test');
+
+ $this->assertEquals('', $item);
+ }
+
+ public function testCanCreateTextareaWithNameAndId(): void
+ {
+ $item = LaravelForm::textarea()->name('test')->id('test');
+
+ $this->assertEquals('', $item);
+ }
+
+ public function testCanCreateTextareaWithCustomAttribute(): void
+ {
+ $item = LaravelForm::textarea()->name('test')->attribute('data-test', 'test');
+
+ $this->assertEquals('', $item);
+ }
+
+ public function testCanCreateTextareaWithErrors(): void
+ {
+ $this->setErrors();
+
+ $item = LaravelForm::textarea()->name('test');
+
+ $this->assertEquals(
+ expected: 'error',
+ actual: $item
+ );
+ }
+
+ public function testCanCreateTextareaWithValue(): void
+ {
+ $item = LaravelForm::textarea()->value('test');
+
+ $this->assertEquals('', $item);
+ }
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
new file mode 100644
index 0000000..70ee8bc
--- /dev/null
+++ b/tests/TestCase.php
@@ -0,0 +1,27 @@
+startSession();
+ $this->withSession(['errors' => (new ViewErrorBag)->put('default', new MessageBag(['test' => 'error']))]);
+ }
+}