PHP code example of b2pweb / bdf-form

1. Go to this page and download the library: Download b2pweb/bdf-form library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

b2pweb / bdf-form example snippets




namespace App\Form;

use Bdf\Form\Aggregate\FormBuilderInterface;
use Bdf\Form\Custom\CustomForm;

class LoginForm extends CustomForm
{
    protected function configure(FormBuilderInterface $builder) : void
    {
        // Register inputs using builder
        // ltiple actions in one form)
        $builder->submit('login');
    }
}


// Instantiate the form (a container can be use for handle dependency injection)
$form = new LoginForm();
$view = $form->view(); // Get the form view




// Instantiate the form (a container can be use for handle dependency injection)
$form = new LoginForm();

// Submit and check if the form is valid
if (!$form->submit($_POST)->valid()) {
    // The form has an error : use `ElementInterface::error()` to get the error and render it
    echo 'Error : ', $form->error();
    return;
}

// The form is valid : get the value
$credentials = $form->value();

// $credentials is an array with elements values 
performLogin($credentials['username'], $credentials['password']);



// Declare the entity
// The properties should be public, or has public accessors to be handled by the form
class Person
{
    /** @var string */
    public $firstName;
    /** @var string */
    public $lastName;
    /** @var DateTimeInterface|null */
    public $birthDate;
    /** @var Country|null */
    public $country;
}

class PersonForm extends \Bdf\Form\Custom\CustomForm
{
    protected function configure(\Bdf\Form\Aggregate\FormBuilderInterface $builder) : void
    {
        // Define that PersonForm::value() should return a Person instance 
        $builder->generates(Person::class);
        
        // Declare fields with getter and setter
        $builder->string('firstName')->



class PersonController extends Controller
{
    private $repository;
    
    // Get a form view with entity values
    public function editForm($request)
    {
        // Get the entity
        $person = $this->repository->find($request->query->get('id'));
        
        // Create the form, import the entity data, and create the view object
        $form = new PersonForm();
        $view = $form->import($person)->view();
        
        // The form view can be used: fields values are set
        return $this->render('person/form', ['form' => $view]);
    }
    
    // Use the form to create the entity
    public function create($request)
    {
        // Get the form instance
        $form = new PersonForm();
        
        // Submit form data
        if (!$form->submit($request->post())->valid()) {
            throw new FormError($form->error());
        }
        
        // $form->value() will return the filled entity
        $this->repository->insert($form->value());
    }
    
    // Update an existent entity: simply attach the entity to fill
    public function update($request)
    {
        // Get the entity
        $person = $this->repository->find($request->query->get('id'));
        
        // Get the form instance and attach the entity to update
        $form = new PersonForm();
        $form->attach($person);

        // Submit form data
        if (!$form->submit($request->post())->valid()) {
            throw new FormError($form->error());
        }
        
        // $form->value() will return the filled entity
        $this->repository->insert($form->value());
    }
    
    // Works like update, but apply only provided fields (HTTP PATCH method)
    // The entity must be import()'ed instead of attach()'ed
    public function patch($request)
    {
        // Get the entity
        $person = $this->repository->find($request->query->get('id'));
        
        // Get the form instance and import the entity to patch
        $form = new PersonForm();
        $form->import($person);

        // Submit form data
        if (!$form->patch($request->post())->valid()) {
            throw new FormError($form->error());
        }
        
        // $form->value() will return the filled entity
        $this->repository->insert($form->value());
    }
}

// Declare a transformer
class JsonTransformer implements \Bdf\Form\Transformer\TransformerInterface
{
    public function transformToHttp($value,\Bdf\Form\ElementInterface $input)
    {
        return json_encode($value);
    }

    public function transformFromHttp($value,\Bdf\Form\ElementInterface $input)
    {
        return json_decode($value, true);
    }
}

class MyForm extends CustomForm
{
    protected function configure(FormBuilderInterface $builder): void
    {
        // Transform JSON input to associative array
        $builder->transformer(new JsonTransformer());
    }
}

// Filter for keep only alpha numeric characters
class AlphaNumFilter implements \Bdf\Form\Filter\FilterInterface
{
    public function filter($value,\Bdf\Form\Child\ChildInterface $input,$default)
    {
        if (!is_string($value)) {
            return null;
        }

        return preg_replace('/[^a-z0-9]/i', '', $value);
    }
}

class MyForm extends CustomForm
{
    protected function configure(FormBuilderInterface $builder): void
    {
        $builder->string('foo')->filter(new AlphaNumFilter())->setter();
    }
}

$form = new MyForm();
$form->submit(['foo' => '$$$abc123___']);
$form->value()['foo'] === 'abc123'; // The string is filtered

// Declare a transformer
class Base64Transformer implements \Bdf\Form\Transformer\TransformerInterface
{
    public function transformToHttp($value,\Bdf\Form\ElementInterface $input)
    {
        return base64_encode($value);
    }

    public function transformFromHttp($value,\Bdf\Form\ElementInterface $input)
    {
        $value = base64_decode($value);
        
        // Throw exception on invalid value
        if ($value === false) {
            throw new InvalidArgumentException('Invalid base64 data');
        }

        return $value;
    }
}

class MyForm extends CustomForm
{
    protected function configure(FormBuilderInterface $builder): void
    {
        // "foo" is a base64 string input
        $builder
            ->string('foo')
            ->transformer(new Base64Transformer())
            ->transformerErrorMessage('Expecting base 64 data') // Define custom transformer error code and message
            ->transformerErrorCode('INVALID_BASE64_ERROR')
        ;
    }
}

class MyForm extends CustomForm
{
    protected function configure(FormBuilderInterface $builder): void
    {
        // The date should be saved as timestamp on the entity
        $builder
            ->dateTime('date')
            ->saveAsTimestamp()
            ->setter()
        ;

        // Save data as mongodb Binary        
        $builder
            ->string('data')
            ->modelTransformer(function ($value, $input, $toModel) {
                return $toModel ? new Binary($value, Binary::TYPE_GENERIC) : $value->getData();
            })
            ->setter()
        ;
    }
}

class MyForm extends CustomForm
{
    protected function configure(FormBuilderInterface $builder): void
    {
        $builder->generates(MyEntity::class);

        $builder->string('foo')->setter();
        $builder->string('bar')->setter();
        
        $builder->satisfy(function (MyEntity $entity) {
            // Validate the hydrated entity
            if (!$entity->isValid()) {
                return 'Invalid entity';
            }
        });
    }
}

 

class UserForm extends \Bdf\Form\Custom\CustomForm
{
    protected function configure(\Bdf\Form\Aggregate\FormBuilderInterface $builder): void
    {
        // Define a sub-form "credentials", which generates a Credentials object
        $builder->embedded('credentials', function (\Bdf\Form\Child\ChildBuilderInterface $builder) {
            // $builder is type of ChildBuilderInterface, but forward call to FormBuilderInterface
            // So it can be used like a simple form builder
            
            $builder->generates(Credentials::class);
            $builder->string('username')->    // embedded and leaf fields can be mixed on the same form 
        $builder->string('email')->

 

class CredentialsForm extends \Bdf\Form\Custom\CustomForm
{
    protected function configure(\Bdf\Form\Aggregate\FormBuilderInterface $builder) : void
    {
        $builder->generates(Credentials::class);
        $builder->string('username')-> {
        $builder->generates(Address::class);
        $builder->string('address')->ace $builder) : void
    {
        // Simply define element with the embedded form class name
        $builder->add('credentials', CredentialsForm::class);
        $builder->array('addresses', AddressForm::class);
        $builder->string('email')->



class UserForm extends \Bdf\Form\Custom\CustomForm
{
    protected function configure(\Bdf\Form\Aggregate\FormBuilderInterface $builder) : void
    {
        // Simply define element with the embedded form class name
        $builder->add('credentials', CredentialsForm::class)->prefix();
        $builder->array('addresses', AddressForm::class);
        $builder->string('email')->

 
// Using "low level" FieldPath helper
class CredentialsForm extends \Bdf\Form\Custom\CustomForm
{
    protected function configure(\Bdf\Form\Aggregate\FormBuilderInterface $builder) : void
    {
        $builder->string('username');
        $builder->string('password');
        $builder->string('confirm')
            ->depends('password') // Password must be submitted before confirm
            ->satisfy(function ($value, \Bdf\Form\ElementInterface $input) {
                // Get sibling field value using FieldPath
                // Note: with FieldPath, the path is relative to the parent of the current field
                if ($value !== \Bdf\Form\Util\FieldPath::parse('password')->value($input)) {
                    return 'Confirm must be same as password';
                }
            })
        ;
    }
}

// Using FieldFinderTrait on custom form
class CredentialsForm extends \Bdf\Form\Custom\CustomForm
{
    use \Bdf\Form\Util\FieldFinderTrait;

    protected function configure(\Bdf\Form\Aggregate\FormBuilderInterface $builder) : void
    {
        $builder->string('username');
        $builder->string('password');
        $builder->string('confirm')
            ->depends('password') // Password must be submitted before confirm
            ->satisfy(function ($value, \Bdf\Form\ElementInterface $input) {
                // Get sibling field value using findFieldValue
                // Note: with FieldFinderTrait, the path is relative to the custom form
                if ($value !== $this->findFieldValue('password')) {
                    return 'Confirm must be same as password';
                }
            })
        ;
    }
}

use Bdf\Form\Util\FieldPath;

$form = new UserForm();

// Find the username field, starting from the root
// Start the expression with "." to not start the path from the parent of UserForm (which do not exists)
$username = FieldPath::parse('./embedded/username')->resolve($form);

// Also works from a "leaf" field
$password = FieldPath::parse('password')->resolve($username);
// Same as above
$password = FieldPath::parse('../password')->resolve($username);

// Absolute path : get "email" field of the root form
$email = FieldPath::parse('/email')->resolve($username);

$builder->string('country')
    ->choices([
        'France' => 'FR',
        'United-Kingdom' => 'UK',
        'Spain' => 'ES',
    ])
;

<select name=" echo $view->name(); 



class MyForm extends \Bdf\Form\Custom\CustomForm
{
    const SAVE_BTN = 'save';
    const DELETE_BTN = 'delete';

    protected function configure(\Bdf\Form\Aggregate\FormBuilderInterface $builder) : void
    {
        $builder->string('foo');
        
        // Define buttons
        $builder->submit(self::SAVE_BTN);
        $builder->submit(self::DELETE_BTN);
    }
}

 
$view = (new MyForm())->view();



$form = new MyForm();

// Submit the form 
$form->submit($_POST);

// Get the submitted button name
// The submit button is handled by the root element
switch ($btn = $form->root()->submitButton() ? $btn->name() : null) {
    case MyForm::SAVE_BTN:
        doSave($form->value());
        break;

    case MyForm::DELETE_BTN:
        doDelete($form->value());
        break;
        
    default:
        throw new Exception();
}

$builder->string('username')
    ->length(['min' => 3, 'max' => 32]) // Define length options
    ->regex('/[a-z0-9_-]+/i') // Define a validation regex
;

$builder->email('username')->mode(Email::VALIDATION_MODE_HTML5);

$builder->url('server')->protocols('ftp', 'sftp');

$builder->integer('number')
    ->posivite() // Same as ->min(0)
    ->min(5) // The number must be >= 5
    ->max(9999) // The element must be <= 9999
    ->grouping(true) // The HTTP value will group values by thousands (i.e. 145 000 instead of 145000)
    ->raw(false) // If set to true, the input will be simply caster to int, and not transformed following the locale
;

$builder->float('number')
    ->posivite() // Same as ->min(0)
    ->min(1.1) // The number must be >= 1.1
    ->max(99.99) // The element must be <= 99.99
    ->grouping(true) // The HTTP value will group values by thousands (i.e. 145 000 instead of 145000)
    ->scale(2) // Only consider 2 digit on the decimal part
    ->raw(false) // If set to true, the input will be simply caster to int, and not transformed following the locale
;

$builder->boolean('enabled')
    ->httpValue('enabled') // Define the 

$builder->dateTime('eventDate')
    ->className(Carbon::class) // Define a custom date class
    ->immutable() // Same as ->className(DateTimeImmutable::class)
    ->format('d/m/Y H:i') // Define the parsed date format
    ->timezone('Europe/Paris') // Define the parse timezone. The element PHP value will be on this timezone
    ->before(new DateTime('+1 year')) // eventDate must be before next year
    ->beforeField('eventDateEnd') // Compare the field value to a sibling field (eventDateEnd)
    ->after(new DateTime()) // eventDate must be after now
    ->afterField('accessDate') // Compare the field value to a sibling field (accessDate)

$builder->phone('contact')
    ->regionResolver(function () {
        return $this->user()->country(); // Resolve the phone region using a custom resolver
    })
    ->region('FR') // Force the region value for parse the phone number
    ->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)
    ->setter()->saveAsString() // Save the phone number as string on the entity
;

$builder->csrf() // No need to set a name. by default "_token"
    ->tokenId('my_token') // Define the token id. By default is use CsrfElement::class, but define a custom tokenId permit to not share CSRF token between forms
    ->message('The token is invalid') // Define a custom error message
    ->invalidate(true) // The token is always invalidated after check, and should be regenerated
;

$builder->add('foo', AnyElement::class) // No helper method are present
    ->satisfy(function ($value) { ... }) // Configure the element
    ->transform(function ($value) { ... })
;

use Bdf\Form\Aggregate\FormBuilderInterface;
use Bdf\Form\Custom\CustomForm;
use Bdf\Form\ElementInterface;
use Bdf\Form\Transformer\TransformerInterface;

// Declare the element class
class UriElement extends CustomForm
{
    const INNER_ELEMENT = 'uri';

    /**
    * @var UriFactoryInterface 
    */
    private $uriFactory;
    
    // Set the UriFactoryInterface at the constructor
    public function __construct(?FormBuilderInterface $builder = null, ?UriFactoryInterface $uriFactory = null) 
    {
        parent::__construct($builder);
        
        $this->uriFactory = $uriFactory ?? new DefaultUriFactory(); // Default instance for the factory
    }

    protected function configure(FormBuilderInterface $builder) : void
    {
        // Define inner element to store value
        // The URI is basically a string
        $builder->string(self::INNER_ELEMENT);

        // The UriElement is a form, and supports only array values
        // Define a transformer to remap value to the inner element
        $builder->transformer(new class implements TransformerInterface {
            public function transformToHttp($value, ElementInterface $input)
            {
                // $value is an array of children value
                // Return only the inner element value
                return $value[UriElement::INNER_ELEMENT];
            }
            
            public function transformFromHttp($value, ElementInterface $input)
            {
                // $value is the URI string
                // Map it as array to the inner element
                return [UriElement::INNER_ELEMENT => $value];
            }           
        });

        // Define the value generator : parse the inner element value to UriInterface using the factory        
        $builder->generates(function () {
            return $this->uriFactory->createUri($this[self::INNER_ELEMENT]->element()->value());
        });
    }
}

use Bdf\Form\Choice\ChoiceInterface;
use Bdf\Form\Leaf\LeafElement;
use Bdf\Form\Transformer\TransformerInterface;
use Bdf\Form\Validator\ValueValidatorInterface;

// Declare the element using LeafElement class
class UriElement extends LeafElement
{
    /**
     * @var UriFactory 
     */
    private $uriFactory;

    // Overrides constructor to add the factory
    public function __construct(?ValueValidatorInterface $validator = null, ?TransformerInterface $transformer = null, ?ChoiceInterface $choices = null, ?UriFactory $uriFactory = null) 
    {
        parent::__construct($validator, $transformer, $choices);
        
        $this->uriFactory = $uriFactory ?? new DefaultUriFactory(); // Use default instance
    }

    // Parse the HTTP string value using the factory
    protected function toPhp($httpValue): ?UriInterface
    {
        return $httpValue ? $this->uriFactory->createUri($httpValue) : null;
    }
    
    // Stringify the UriInterface instance
    protected function toHttp($phpValue): ?string
    {
        return $phpValue === null ? null : (string) $phpValue;
    }
}

use Bdf\Form\AbstractElementBuilder;
use Bdf\Form\Choice\ChoiceBuilderTrait;
use Bdf\Form\ElementInterface;

// Declare the builder
class UriElementBuilder extends AbstractElementBuilder
{
    use ChoiceBuilderTrait; // Enable choices building
    
    /**
     * @var UriFactory
     */
    private $uriFactory;
    
    public function __construct(?RegistryInterface $registry = null, ?UriFactory $uriFactory = null) 
    {
        parent::__construct($registry);

        $this->uriFactory = $uriFactory ?? new DefaultUriFactory();
    }
    
    // You can define custom builder methods for constrains or 
    public function host(string $hostName): self
    {
        return $this->satisfy(function (?UriInterface $uri) use($hostName) {
            if ($uri->getHost() !== $hostName) {
                return 'Invalid host name';
            }
        });
    }

    // Create the element
    protected function createElement(ValueValidatorInterface $validator, TransformerInterface $transformer) : ElementInterface
    {
        return new UriElement($validator, $transformer, $this->getChoices(), $this->uriFactory);
    }
}

// Register the element builder on the registry
// Use container to inject dependencies (here the UriFactoryInterface)
$registry->register(UriElement::class, function(Registry $registry) use($container) {
    return new UriElementBuilder($registry, $container->get(UriFactoryInterface::class));
});

$builder->add('uri', UriElement::class);

class MyCustomChildBuilder extends ChildBuilder
{
    public function __construct(string $name, ElementBuilderInterface $elementBuilder, RegistryInterface $registry = null)
    {
        parent::__construct($name, $elementBuilder, $registry);

        // Add a filter provider
        $this->addFilterProvider([$this, 'provideFilter']);
    }
    
    // Model transformer helper method
    public function saveAsCustom()
    {
        return $this->modelTransformer(new MyCustomTransformer());
    }
    
    // Provide default filter
    protected function provideFilter()
    {
        return [new MyFilter()];
    }
}

// Now you can register the child builder with the element builder
$registry->register(CustomElement::class, CustomElementBuilder::class, MyCustomChildBuilder::class);

if (!$form->error()->empty()) {
    // Simple render errors as string
    return new Response('<div class="alert alert-danger">'.$form->error().'</div>');
    
    // For simple API error system, use toArray()
    return new JsonReponse(['error' => $form->error()->toArray()]);
    
    // Or use accessors
    foreach ($form->error()->children() as $name => $error) {
        echo $error->field().' : '.$error->global().' ('.$error->code().')'.PHP_EOL;
    }
}

/**
 * Print errors in format [httpField => ['code' => errorCode, 'message' => errorMessage]]
 */
class ApiPrinter implements \Bdf\Form\Error\FormErrorPrinterInterface
{
    private $errors = [];
    private $current;

    // Define the error message for the current element
    public function global(string $error) : void
    {
        $this->errors[$this->current]['message'] = $error;
    }
    
    // Define the error code for the current element
    public function code(string $code) : void
    {
        $this->errors[$this->current]['code'] = $code;
    }
    
    // Define the error element http field
    public function field(\Bdf\Form\Child\Http\HttpFieldPath $field) : void
    {
        $this->current = $field->get();
    }
    
    // Iterate on element's children
    public function child(string $name,\Bdf\Form\Error\FormError $error) : void
    {
        $error->print($this);
    }
    
    // Get all errors
    public function print()
    {
        return $this->errors;
    }
}

// Usage
return new JsonReponse(['errors' => $form->error()->print(new ApiPrinter())]);