Skip to content

Commit

Permalink
Refactor choice list widgets and add immutability tests. (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
terabytesoftw committed Jan 9, 2024
1 parent bf84d2a commit 0176c7e
Show file tree
Hide file tree
Showing 27 changed files with 1,044 additions and 702 deletions.
5 changes: 5 additions & 0 deletions infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@
"PHPForge\\Html\\Attribute\\HasId::id",
"PHPForge\\Html\\Attribute\\Input\\CanBeChecked::checked",
"PHPForge\\Html\\Attribute\\Input\\CanBeRequired::required",
"PHPForge\\Html\\Attribute\\Input\\HasMaxLength::maxLength",
"PHPForge\\Html\\Attribute\\Input\\HasMinLength::minLength",
"PHPForge\\Html\\Attribute\\Input\\HasName::name",
"PHPForge\\Html\\Attribute\\Input\\HasPattern::pattern",
"PHPForge\\Html\\Attribute\\Input\\HasPlaceholder::placeholder",
"PHPForge\\Html\\Attribute\\Input\\HasValue::value",
"PHPForge\\Html\\Input\\Text::maxLength()",
"PHPForge\\Html\\Input\\Text::minLength()"
]
}
}
Expand Down
46 changes: 31 additions & 15 deletions src/Attribute/Custom/HasWidgetValidation.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,18 @@
*/
trait HasWidgetValidation
{
private function validateTagName(string $tagName, string ...$compare): void
{
if (in_array($tagName, $compare, true) === false) {
throw new InvalidArgumentException(
sprintf('%s::class widget must have a tag name of %s.', static::class, implode(', ', $compare))
);
}
}

/**
* Validate if the value is numeric or null based on the type.
* Validate if the value is a iterable or null based on the type.
*
* @param mixed $value The value to validate.
*
* @throws InvalidArgumentException If the value is invalid.
*/
private function validateNumericValue(mixed $value): void
protected function validateIterable(mixed $value): void
{
if ($value !== null && $value !== '' && !is_numeric($value)) {
if (!is_iterable($value) && $value !== null) {
throw new InvalidArgumentException(
sprintf('%s::class widget must be a numeric or null value.', static::class)
sprintf('%s::class widget must be an iterable or null value.', static::class)
);
}
}
Expand All @@ -43,7 +34,7 @@ private function validateNumericValue(mixed $value): void
*
* @throws InvalidArgumentException If the value is invalid.
*/
private function validateScalar(mixed ...$values): void
protected function validateScalar(mixed ...$values): void
{
foreach ($values as $value) {
if (is_scalar($value) === false && $value !== null) {
Expand All @@ -52,19 +43,44 @@ private function validateScalar(mixed ...$values): void
}
}

/**
* Validate if the value is numeric or null based on the type.
*
* @param mixed $value The value to validate.
*
* @throws InvalidArgumentException If the value is invalid.
*/
private function validateNumeric(mixed $value): void
{
if ($value !== null && $value !== '' && !is_numeric($value)) {
throw new InvalidArgumentException(
sprintf('%s::class widget must be a numeric or null value.', static::class)
);
}
}

/**
* Validate if the value is a string or null based on the type.
*
* @param mixed $value The value to validate.
*
* @throws InvalidArgumentException If the value is invalid.
*/
private function validateStringValue(mixed $value): void
private function validateString(mixed $value): void
{
if ($value !== null && !is_string($value)) {
throw new InvalidArgumentException(
sprintf('%s::class widget must be a string or null value.', static::class)
);
}
}

private function validateTagName(string $tagName, string ...$compare): void
{
if (in_array($tagName, $compare, true) === false) {
throw new InvalidArgumentException(
sprintf('%s::class widget must have a tag name of %s.', static::class, implode(', ', $compare))
);
}
}
}
2 changes: 1 addition & 1 deletion src/Attribute/Input/HasMaxLength.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ trait HasMaxLength
*
* @link https://html.spec.whatwg.org/multipage/input.html#attr-input-maxlength
*/
public function maxlength(int $value): static
public function maxLength(int $value): static
{
$new = clone $this;
$new->attributes['maxlength'] = $value;
Expand Down
2 changes: 1 addition & 1 deletion src/Attribute/Input/HasMinLength.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ trait HasMinLength
*
* @link https://html.spec.whatwg.org/multipage/input.html#attr-input-minlength
*/
public function minlength(int $value): static
public function minLength(int $value): static
{
$new = clone $this;
$new->attributes['minlength'] = $value;
Expand Down
2 changes: 1 addition & 1 deletion src/Input/Base/AbstractButton.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function loadDefaultDefinitions(): array

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

$attributes = $this->attributes;
$labelFor = $this->labelFor ?? $this->id;
Expand Down
36 changes: 16 additions & 20 deletions src/Input/Base/AbstractChoiceList.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace PHPForge\Html\Input\Base;

use PHPForge\Html\Attribute;
use PHPForge\Html\Helper\Utils;
use PHPForge\Html\Input\Checkbox;
use PHPForge\Html\Input\Contract;
use PHPForge\Html\Input\Radio;
Expand Down Expand Up @@ -43,26 +44,13 @@ abstract class AbstractChoiceList extends Element implements
protected array $items = [];

/**
* This method is used to configure the widget with the provided default definitions.
* Generate the HTML representation of CheckboxList or RadioList.
*
* @param string $type The type of the list.
*
* @return string The HTML representation of the element.
*/
public function loadDefaultDefinitions(): array
{
return [
'container()' => [true],
'id()' => [$this->generateId('choice-')],
'template()' => ['{label}\n{tag}'],
];
}

public function items(Checkbox|Radio ...$items): static
{
$new = clone $this;
$new->items = $items;

return $new;
}

protected function run(): string
protected function buildChoiceListTag(string $type): string
{
$attributes = $this->attributes;
$containerAttributes = $this->containerAttributes;
Expand All @@ -84,12 +72,20 @@ protected function run(): string
unset($attributes['tabindex']);
}

if (array_key_exists('name', $attributes) && is_string($attributes['name']) && $type === 'checkbox') {
$attributes['name'] = Utils::generateArrayableName($attributes['name']);
}

unset($attributes['value']);

foreach ($this->items as $item) {
$item = match ($type) {
'checkbox' => $item->checked(in_array($item->getValue(), (array) $this->checkedValue)),
'radio' => $item->checked($item->getValue() === $this->checkedValue),
};

$listItem = $item
->attributes($attributes)
->checked($this->checkedValue === $item->getValue())
->enclosedByLabel($this->enclosedByLabel)
->labelClass($this->labelItemClass)
->separator($this->separator);
Expand Down
2 changes: 1 addition & 1 deletion src/Input/Base/AbstractHidden.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function loadDefaultDefinitions(): array

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

return Tag::widget()
->attributes($this->attributes)
Expand Down
41 changes: 41 additions & 0 deletions src/Input/CheckboxList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace PHPForge\Html\Input;

/*
* Generates a list of checkboxes buttons.
*
* A checkbox is a graphical control element that allows the user to choose one or more options from a predefined set of
* mutually exclusive options.
*/
final class CheckboxList extends Base\AbstractChoiceList
{
public function items(Checkbox ...$items): static
{
$new = clone $this;
$new->items = $items;

return $new;
}

/**
* This method is used to configure the widget with the provided default definitions.
*/
public function loadDefaultDefinitions(): array
{
return [
'container()' => [true],
'id()' => [$this->generateId('checkboxlist-')],
'template()' => ['{label}\n{tag}'],
];
}

protected function run(): string
{
$this->validateIterable($this->checkedValue);

return $this->buildChoiceListTag('checkbox');
}
}
14 changes: 0 additions & 14 deletions src/Input/ChoiceList.php

This file was deleted.

2 changes: 1 addition & 1 deletion src/Input/Color.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class Color extends Base\AbstractInput implements Contract\RequiredInterfa

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

return $this->buildInputTag($this->attributes, 'color');
}
Expand Down
4 changes: 2 additions & 2 deletions src/Input/Contract/LengthInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface LengthInterface
*
* @link https://html.spec.whatwg.org/multipage/input.html#attr-input-maxlength
*/
public function maxlength(int $value): static;
public function maxLength(int $value): static;

/**
* Set the minimum number of characters (as UTF-16 code units) the user can enter into the text input.
Expand All @@ -36,5 +36,5 @@ public function maxlength(int $value): static;
*
* @link https://html.spec.whatwg.org/multipage/input.html#attr-input-minlength
*/
public function minlength(int $value): static;
public function minLength(int $value): static;
}
2 changes: 1 addition & 1 deletion src/Input/Date.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class Date extends Base\AbstractInput implements Contract\RangeLengthInter

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

return $this->buildInputTag($this->attributes, 'date');
}
Expand Down
2 changes: 1 addition & 1 deletion src/Input/Datetime.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class Datetime extends Base\AbstractInput implements Contract\RangeLengthI

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

return $this->buildInputTag($this->attributes, 'datetime');
}
Expand Down
2 changes: 1 addition & 1 deletion src/Input/DatetimeLocal.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class DatetimeLocal extends Base\AbstractInput implements Contract\RangeLe

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

return $this->buildInputTag($this->attributes, 'datetime-local');
}
Expand Down
2 changes: 1 addition & 1 deletion src/Input/Month.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class Month extends Base\AbstractInput implements Contract\RangeLengthInte

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

return $this->buildInputTag($this->attributes, 'month');
}
Expand Down
41 changes: 41 additions & 0 deletions src/Input/RadioList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace PHPForge\Html\Input;

/*
* Generates a list of radio buttons.
*
* A radio button is a graphical control element that allows the user to choose only one of a predefined set of mutually
* exclusive options.
*/
final class RadioList extends Base\AbstractChoiceList
{
public function items(Radio ...$items): static
{
$new = clone $this;
$new->items = $items;

return $new;
}

/**
* This method is used to configure the widget with the provided default definitions.
*/
public function loadDefaultDefinitions(): array
{
return [
'container()' => [true],
'id()' => [$this->generateId('radiolist-')],
'template()' => ['{label}\n{tag}'],
];
}

protected function run(): string
{
$this->validateScalar($this->checkedValue);

return $this->buildChoiceListTag('radio');
}
}
2 changes: 1 addition & 1 deletion src/Input/Range.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final class Range extends Base\AbstractInput implements Contract\RangeLengthInte

protected function run(): string
{
$this->validateNumericValue($this->getValue());
$this->validateNumeric($this->getValue());

return $this->buildInputTag($this->attributes, 'range');
}
Expand Down
2 changes: 1 addition & 1 deletion src/Input/Text.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ final class Text extends Base\AbstractInput implements

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

return $this->buildInputTag($this->attributes, 'text');
}
Expand Down
2 changes: 1 addition & 1 deletion src/Input/Time.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class Time extends Base\AbstractInput implements Contract\RangeLengthInter

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

return $this->buildInputTag($this->attributes, 'time');
}
Expand Down
2 changes: 1 addition & 1 deletion src/Input/Week.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class Week extends Base\AbstractInput implements Contract\RangeLengthInter

protected function run(): string
{
$this->validateStringValue($this->getValue());
$this->validateString($this->getValue());

return $this->buildInputTag($this->attributes, 'week');
}
Expand Down

0 comments on commit 0176c7e

Please sign in to comment.