5.3 KiB
Develop your own field type
← To the section “Document fields”
The new application type comes as an installable module. So his classes, rights, migrations, and deletion remain one managed unit.
1. Type class
Example of a numerical rating:
<?php
namespace App\Modules\Reviews\Fields;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Content\Fields\AbstractFieldType;
use App\Content\Fields\FieldContext;
class RatingField extends AbstractFieldType
{
public function code()
{
return 'rating';
}
public function name()
{
return 'Рейтинг';
}
public function isNumeric()
{
return true;
}
public function settingsSchema()
{
return array(
array('key' => 'maximum', 'type' => 'int',
'label' => 'Максимум', 'default' => 5),
);
}
public function renderEdit(FieldContext $ctx)
{
$max = max(1, min(100, (int) $ctx->setting('maximum', 5)));
return '<input class="input" type="number" min="0" max="' . $max
. '" name="' . $this->attr($ctx->inputName())
. '" value="' . $this->attr($ctx->value) . '">';
}
public function renderView(FieldContext $ctx)
{
if (trim((string) $ctx->value) === '') {
return '';
}
return $this->e($ctx->value) . ' / '
. max(1, (int) $ctx->setting('maximum', 5));
}
public function save(FieldContext $ctx)
{
$raw = trim(str_replace(',', '.', (string) $ctx->value));
if ($raw === '') {
return '';
}
if (!is_numeric($raw)) {
throw new \RuntimeException('Рейтинг должен быть числом');
}
return (string) (0 + $raw);
}
}
AbstractFieldType provides safe e()/attr(), standard
validation and empty filter implementations. The code must be unique and consist of
Latin letters, numbers and _ do not change after release.
2. Placement in a package
modules/reviews/
├── app/
│ ├── module.php
│ └── Fields/
│ └── RatingField.php
└── admin/
└── module.php
The class is in app, and the type must be declared in both runtime-manifest. Public
app/module.php serves the site output:
<?php
use App\Modules\Reviews\Fields\RatingField;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
return array(
'code' => 'reviews',
'name' => 'Отзывы',
'version' => '1.0.0',
'lifecycle' => array('managed' => true),
'field_types' => array(
array('class' => RatingField::class, 'creatable' => true),
),
);
Administrative admin/module.php registers the same class for the constructor
rubrics and document editor:
<?php
use App\Modules\Reviews\Fields\RatingField;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
return array(
'code' => 'reviews',
'name' => 'Отзывы',
'version' => '1.0.0',
'lifecycle' => array('managed' => true),
'package' => array('removable' => true),
'field_types' => array(
array('class' => RatingField::class, 'creatable' => true),
),
'admin_extension' => array(
'url' => '/reviews',
'icon' => 'ti ti-message-star',
'feature' => 'Отзывы и тип поля рейтинга',
),
);
For a composite package, namespace App\Modules\Reviews automatically specifies
to modules/reviews/app and is available to both runtimes. Manual require is not needed.
If the type is declared only in app/module.php, the public site will read it, but
The control panel won't offer it in the designer; only in admin/module.php - reverse
error.
creatable = false is only used for a type that must continue
serve existing fields, but should no longer be offered for creation.
3. Installation and activation
- Install the package in System → Modules.
- Open Categories and Fields → Field Types.
- Enable the new type to create.
- Create a test rubric and field.
- Check for empty, valid, and invalid values.
- Check the output both on the document page and in the query.
- Check the JSON API of the document: the type and normalized value must be predictable for external integration.
4. When filters are needed
A type participates in a directory if it overrides:
public function getFilterTypes();
public function buildFilterOptions($fieldId, $rubricId);
public function normalizeFilterValue($value);
public function applyFilter($fieldId, $selected);
applyFilter() returns array('sql' => ' ...', 'args' => array(...)).
You cannot insert a custom value directly into SQL. Use
DB placeholders and limit the allowed options.
5. Review before release
- all PHP passes
php -lon PHP 7.3; code()does not conflict with installed types;- scalar is stored as a string, the structure is JSON;
renderEdit()uses$ctx->inputName();- user data is escaped in HTML;
save()normalizes the same input the same;- an empty value does not turn into a dummy
0or an empty object; - the module cannot be deleted while the type is being used by the category;
- the new storage version is accompanied by migration and a description of the update.