Skip to content

Commit

Permalink
Fix compatibility with old library
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent4vx committed Feb 5, 2021
1 parent 5b91c4e commit ecf04c2
Show file tree
Hide file tree
Showing 36 changed files with 525 additions and 45 deletions.
21 changes: 1 addition & 20 deletions README.md
Expand Up @@ -27,7 +27,6 @@ Library for handle form, and request validation.
- [BooleanElement](#booleanelement)
- [DateTimeElement](#datetimeelement)
- [PhoneElement](#phoneelement)
- [FormattedPhoneElement](#formattedphoneelement)
- [CsrfElement](#csrfelement)
- [Create a custom element](#create-a-custom-element)
- [Using custom form](#using-custom-form)
Expand Down Expand Up @@ -682,7 +681,6 @@ $builder->dateTime('eventDate')

Handle phone number. The package `giggsey/libphonenumber-for-php` is required to use this element.
This element will not return a `string` but a `PhoneNumber` instance.
If you want to save the phone as string, use `FormattedPhoneElement` instead.

```php
$builder->phone('contact')
Expand All @@ -693,24 +691,7 @@ $builder->phone('contact')
->regionInput('address/country') // Use a sibling input for parse the number (do not forget to call `depends()`)
->allowInvalidNumber(true) // Do not check the phone number
->validateNumber('My error message') // Enable validation, and define the validator options (here the error message)
;
```

### FormattedPhoneElement

Handle phone number but save a `string` instead of a `PhoneNumber` instance.
This element and build will use internally `PhoneElement`, so `giggsey/libphonenumber-for-php` is required,
and all transformations or validations will takes a `PhoneNumber` object as parameter.

```php
$builder->formattedPhone('contact')
->format(PhoneNumberFormat::NATIONAL) // Define the internal phone format
->regionInput('address/country') // PhoneElementBuilder methods can be used
->allowInvalidNumber(true)
// satisfy and transformer takes a PhoneNumber instance as value
->satifsy(function (\libphonenumber\PhoneNumber $value) {
return $value->hasNationalNumber();
})
->setter()->saveAsString() // Save the phone number as string on the entity
;
```

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -24,7 +24,8 @@
"psr/container": "~1.0",
"symfony/form": "~4.3|~5.0",
"symfony/property-access": "~4.3|~5.0",
"symfony/validator": "~4.3|~5.0"
"symfony/validator": "~4.3|~5.0",
"symfony/polyfill-php80": "~1.22"
},
"require-dev": {
"symfony/security-csrf": "~4.3|~5.0",
Expand Down
62 changes: 62 additions & 0 deletions src/Aggregate/ArrayChildBuilder.php
@@ -0,0 +1,62 @@
<?php

namespace Bdf\Form\Aggregate;

use Bdf\Form\Child\ChildBuilder;
use Bdf\Form\ElementBuilderInterface;
use Bdf\Form\Filter\EmptyArrayValuesFilter;
use Bdf\Form\Registry\RegistryInterface;

/**
* Child builder for ArrayElement
*
* @extends ChildBuilder<ArrayElementBuilder>
*/
class ArrayChildBuilder extends ChildBuilder
{
/**
* @var bool
*/
private $filterEmptyValues = true;

/**
* ArrayChildBuilder constructor.
*
* @param string $name
* @param ArrayElementBuilder $elementBuilder
* @param RegistryInterface|null $registry
*/
public function __construct(string $name, ElementBuilderInterface $elementBuilder, RegistryInterface $registry = null)
{
parent::__construct($name, $elementBuilder, $registry);

$this->addFilterProvider([$this, 'provideEmptyValueFilter']);
}

/**
* Enable or disable filtering empty values into the submitted array
* By default this filter is enabled
*
* Note: are considered empty, the empty string '', the empty array [], and null
*
* @param bool $flag true to enable
*
* @return $this
* @see EmptyArrayValuesFilter
*/
public function filterEmptyValues(bool $flag = true): self
{
$this->filterEmptyValues = $flag;

return $this;
}

protected function provideEmptyValueFilter(): array
{
if (!$this->filterEmptyValues) {
return [];
}

return [EmptyArrayValuesFilter::instance()];
}
}
17 changes: 10 additions & 7 deletions src/Aggregate/ArrayElement.php
Expand Up @@ -177,17 +177,20 @@ public function submit($data): ElementInterface
foreach ($data as $key => $value) {
$child = $lastChildren[$key] ?? (new Child($key, $this->templateElement))->setParent($this);

if (!$child->element()->submit($value)->valid()) {
$this->valid = false;
$errors[$key] = $child->error();
$this->children[$key] = $child;
$child->element()->submit($value);

// Remove null elements
if ($child->element()->value() === null) {
continue;
}

// Remove null elements
if ($child->element()->value() !== null) {
$this->children[$key] = $child;
$this->children[$key] = $child;

if (!$child->element()->valid()) {
$this->valid = false;
$errors[$key] = $child->error();

continue;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Aggregate/FormBuilderInterface.php
Expand Up @@ -203,7 +203,7 @@ public function embedded(string $name, ?callable $configurator = null): ChildBui
* @param class-string<ElementInterface>|null $elementType The inner element type
* @param callable|null $elementConfigurator Callback for configure the inner element
*
* @return ChildBuilderInterface|ArrayElementBuilder
* @return ArrayChildBuilder|ArrayElementBuilder
* @psalm-return ChildBuilderInterface<ArrayElementBuilder>
*
* @see ArrayElementBuilder::element() For the $elementType and $elementConfigurator parameters
Expand Down
27 changes: 24 additions & 3 deletions src/Child/ChildBuilder.php
Expand Up @@ -75,6 +75,11 @@ class ChildBuilder implements ChildBuilderInterface
*/
private $filters = [];

/**
* @var list<callable(RegistryInterface):\Bdf\Form\Filter\FilterInterface[]>
*/
private $filtersProviders = [];

/**
* @var ChildCreationStrategyInterface|callable|class-string<ChildInterface>
*/
Expand Down Expand Up @@ -184,10 +189,14 @@ final public function depends(string ...$inputNames)
*/
final public function buildChild(): ChildInterface
{
$filters = array_map([$this->registry, 'filter'], $this->filters);
$filters = $this->trim ? [TrimFilter::instance()] : [];

if ($this->trim) {
$filters[] = new TrimFilter();
foreach ($this->filtersProviders as $provider) {
$filters = array_merge($filters, $provider($this->registry));
}

foreach ($this->filters as $filter) {
$filters[] = $this->registry->filter($filter);
}

$fields = $this->fields ?: new ArrayOffsetHttpFields($this->name);
Expand Down Expand Up @@ -456,4 +465,16 @@ final protected function registry(): RegistryInterface
{
return $this->registry;
}

/**
* Add a new filter provider
* The filter provider permit to create a filter during the build of the element transformer
* So the filter can be configured by the child builder
*
* @param callable(RegistryInterface):\Bdf\Form\Filter\FilterInterface[] $provider
*/
final protected function addFilterProvider(callable $provider): void
{
$this->filtersProviders[] = $provider;
}
}
4 changes: 3 additions & 1 deletion src/Child/Http/HttpFieldPath.php
Expand Up @@ -2,6 +2,8 @@

namespace Bdf\Form\Child\Http;

use Stringable;

/**
* Represents the HTTP field path to build
* Handles prefix and array key fields
Expand All @@ -15,7 +17,7 @@
* echo $path->prefix('p_')->add('bar')->get(); // Add a prefix for the sub element : "root[p_bar]"
* </code>
*/
final class HttpFieldPath
final class HttpFieldPath implements Stringable
{
/**
* @var HttpFieldPath|null
Expand Down
2 changes: 1 addition & 1 deletion src/Child/Http/PrefixedHttpFields.php
Expand Up @@ -44,7 +44,7 @@ public function extract($httpFields, $defaultValue)
$value = [];

foreach ($data as $name => $datum) {
if (strpos($name, $this->prefix) === 0) {
if (str_starts_with($name, $this->prefix)) {
$value[substr($name, $prefixLen)] = $datum;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Error/FormError.php
Expand Up @@ -6,6 +6,7 @@
use Bdf\Form\Child\Http\HttpFieldPath;
use Bdf\Form\ElementInterface;
use InvalidArgumentException;
use Stringable;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationInterface;

Expand All @@ -15,7 +16,7 @@
* @see ElementInterface::error()
* @see ChildInterface::error()
*/
final class FormError
final class FormError implements Stringable
{
/**
* @var FormError|null
Expand Down
51 changes: 51 additions & 0 deletions src/Filter/EmptyArrayValuesFilter.php
@@ -0,0 +1,51 @@
<?php

namespace Bdf\Form\Filter;

use Bdf\Form\Aggregate\ArrayChildBuilder;
use Bdf\Form\Child\ChildInterface;

/**
* Filter empty values from an array
*
* @see ArrayChildBuilder::filterEmptyValues()
*/
final class EmptyArrayValuesFilter implements FilterInterface
{
/**
* @var EmptyArrayValuesFilter
*/
private static $instance;

/**
* {@inheritdoc}
*/
public function filter($value, ChildInterface $input)
{
if (!is_array($value)) {
return $value;
}

foreach ($value as $k => $v) {
if ($v === null || $v === [] || $v === '') {
unset($value[$k]);
}
}

return $value;
}

/**
* Get the filter instance
*
* @return static
*/
public static function instance(): self
{
if (self::$instance) {
return self::$instance;
}

return self::$instance = new self;
}
}
19 changes: 19 additions & 0 deletions src/Filter/TrimFilter.php
Expand Up @@ -13,6 +13,11 @@
*/
final class TrimFilter implements FilterInterface
{
/**
* @var self
*/
private static $instance;

/**
* {@inheritdoc}
*/
Expand All @@ -29,4 +34,18 @@ public function filter($value, ChildInterface $input)

return trim($value);
}

/**
* Get the trim filter instance
*
* @return static
*/
public static function instance(): self
{
if (self::$instance) {
return self::$instance;
}

return self::$instance = new self;
}
}
2 changes: 1 addition & 1 deletion src/Leaf/BooleanElement.php
Expand Up @@ -64,6 +64,6 @@ protected function toHttp($phpValue)
*/
public function view(?HttpFieldPath $field = null): ElementViewInterface
{
return new BooleanElementView(self::class, (string) $field, $this->httpValue(), $this->httpValue, $this->value(), $this->error()->global());
return new BooleanElementView(self::class, (string) $field, $this->httpValue(), $this->httpValue, (bool) $this->value(), $this->error()->global());
}
}
19 changes: 17 additions & 2 deletions src/Leaf/Date/DateTimeElement.php
Expand Up @@ -35,6 +35,13 @@ final class DateTimeElement extends LeafElement
*/
private $timezone;

/**
* Reset the fields value which are not provided by the format
*
* @var bool
*/
private $resetNotProvidedFields;

/**
* DateTimeType constructor.
*
Expand All @@ -44,14 +51,16 @@ final class DateTimeElement extends LeafElement
* @param class-string<DateTimeInterface> $className The date time class name to use
* @param string $format The time format string
* @param DateTimeZone|null $timezone Timezone to use. Use null to not define a timezone
* @param bool $resetNotProvidedFields Does the fields which are not provided by the format will be reset ? (and set to UNIX time)
*/
public function __construct(?ValueValidatorInterface $validator = null, ?TransformerInterface $transformer = null, ?ChoiceInterface $choices = null, string $className = DateTime::class, string $format = DateTime::ATOM, ?DateTimeZone $timezone = null)
public function __construct(?ValueValidatorInterface $validator = null, ?TransformerInterface $transformer = null, ?ChoiceInterface $choices = null, string $className = DateTime::class, string $format = DateTime::ATOM, ?DateTimeZone $timezone = null, bool $resetNotProvidedFields = true)
{
parent::__construct($validator, $transformer, $choices);

$this->className = $className;
$this->format = $format;
$this->timezone = $timezone;
$this->resetNotProvidedFields = $resetNotProvidedFields;
}

/**
Expand Down Expand Up @@ -97,7 +106,13 @@ protected function toPhp($httpValue): ?DateTimeInterface
throw new \LogicException('Invalid DateTime class name "'.$this->className.'" : method createFromFormat() is not found.');
}

$dateTime = ($this->className)::createFromFormat($this->format, $httpValue, $this->timezone);
$format = $this->format;

if ($this->resetNotProvidedFields && !str_contains($format, '|')) {
$format .= '|';
}

$dateTime = ($this->className)::createFromFormat($format, $httpValue, $this->timezone);
}

if ($dateTime === false) {
Expand Down

0 comments on commit ecf04c2

Please sign in to comment.