Writing your own kit¶
The kits in this module cover Form API basics. A custom kit packages an element you build often (a rating widget, a site-specific address block) so that forms can create it in one line.
1. Extend FormFactoryKit (or FieldKit)¶
FormFactoryKitInterface extends Drupal\kits\KitInterface, which has 15
methods. Rather than implementing them yourself, extend one of the abstract
base classes, which implement all of them:
Drupal\formfactorykits\Kits\FormFactoryKitfor any element;Drupal\formfactorykits\Kits\Field\FieldKitfor an input. It addssetValue(),setDefaultValue(),setPrefix()andsetSuffix().
Set two static properties:
$id: the default array key when no ID is passed;$type: the Form API#typethe kit produces.
2. Compose traits¶
Add use statements for the setters you want (see the
trait reference). Traits that read a key constant from
your class (SizeTrait, PatternTrait, MultipleTrait, ThemeTrait) need
that constant defined.
3. Add your own setters¶
A setter calls $this->set('<property>', $value) and returns static. The
property name is the Form API key without #. For anything that is not a
direct property, override getArray(), call parent::getArray() and adjust
the result.
Example: a 1–5 rating¶
<?php
namespace Drupal\mymodule\Kits;
use Drupal\formfactorykits\Kits\Field\FieldKit;
use Drupal\formfactorykits\Kits\Traits\DescriptionTrait;
use Drupal\formfactorykits\Kits\Traits\RequiredTrait;
use Drupal\formfactorykits\Kits\Traits\TitleTrait;
class RatingKit extends FieldKit {
use DescriptionTrait;
use RequiredTrait;
use TitleTrait;
public static ?string $id = 'rating';
public static ?string $type = 'radios';
public static ?string $title = 'Rating';
private int $max = 5;
public function setMax(int $max): static {
$this->max = $max;
return $this;
}
public function getArray(): array {
$artifact = parent::getArray();
$range = range(1, $this->max);
$artifact['#options'] = array_combine($range, $range);
$artifact['#attributes']['class'][] = 'mymodule-rating';
return $artifact;
}
}
Because $title is set, the constructor gives the kit a translated default
title (t('Rating')) unless one is passed in.
4. Create the kit¶
Every kit has a static create() method that takes the kits service, so the
simplest way to use a custom kit is to call it directly:
use Drupal\mymodule\Kits\RatingKit;
$kits = \Drupal::service('formfactorykits');
$form_factory->append(
RatingKit::create($kits, 'quality')
->setTitle('Quality')
->setMax(3)
->setRequired()
);
This generates:
'quality' => [
'#title' => 'Quality',
'#type' => 'radios',
'#required' => TRUE,
'#options' => [1 => 1, 2 => 2, 3 => 3],
'#attributes' => ['class' => ['mymodule-rating']],
],
5. Optional: add a factory method¶
To call $kits->rating() like the built-in kits, extend the service class and
register your subclass as a service.
<?php
namespace Drupal\mymodule\Services;
use Drupal\formfactorykits\Services\FormFactoryKitsService;
use Drupal\mymodule\Kits\RatingKit;
class MyKitsService extends FormFactoryKitsService {
public function rating(?string $id = NULL, array $parameters = [], array $context = []): RatingKit {
return RatingKit::create($this, $id, $parameters, $context);
}
}
# mymodule.services.yml
services:
mymodule.kits:
class: Drupal\mymodule\Services\MyKitsService
FormObjectBase always injects the formfactorykits service. If you want
your methods available there too, replace that service's class from a service
provider (mymodule/src/MymoduleServiceProvider.php, a class extending
Drupal\Core\DependencyInjection\ServiceProviderBase whose alter() calls
$container->getDefinition('formfactorykits')->setClass(MyKitsService::class)).
Only one module on a site can do this, so prefer a separate service in shared
modules.
Implementing the interface directly¶
If you cannot extend FormFactoryKit, implement FormFactoryKitInterface
yourself. FormFactoryService::getForm() calls isChildrenGrouped(),
getID() and getArray() on every appended kit, and getChildrenArray() on
grouped ones. isChildrenGrouped() is not part of the interface but is
required in practice, because getForm() calls it on every kit.