-
Notifications
You must be signed in to change notification settings - Fork 2
Select
Pair\Html\FormControls\Select renders <select> controls with flat options, object-backed options, or optgroups. It is one of the most important Pair controls because it often sits at the boundary between HTML widgets and application data.
This page is worth reading carefully: Select has more implementation details and caveats than simpler controls like Text or Textarea.
The methods you will use most often are:
options(array|Collection $list, ?string $propertyValue = null, ?string $propertyText = null, ?array $propertyAttributes = null): selfgrouped(array $list): selfempty(?string $text = null): selfmultiple(): selfvalue(string|int|float|DateTime|array|null $value): staticrender(): stringvalidate(): bool
Utility method:
hasOptions(): bool
Inherited helpers from FormControl remain available as usual: label(), required(), disabled(), readonly(), class(), data(), id(), description(), form(), and the other shared methods.
options(...) is the main entry point when a select is built from application data.
It supports two common sources.
$status = (new \Pair\Html\FormControls\Select('status'))
// Key becomes the <option value>, value becomes the visible label.
->options([
'draft' => 'Draft',
'published' => 'Published',
'archived' => 'Archived',
]);$country = (new \Pair\Html\FormControls\Select('countryId'))
// Pair reads id as the option value and name as the visible text.
->options($countries, 'id', 'name')
// Keep the existing record selected while editing.
->value(39);$country = (new \Pair\Html\FormControls\Select('countryId'))
// propertyAttributes adds custom attributes to every generated option.
->options($countries, 'id', 'name', ['iso2', 'data-region'])
->value(39);If propertyText ends with (), Pair calls that method on each object.
$assignee = (new \Pair\Html\FormControls\Select('assigneeId'))
// displayName() is called on each object to build the visible label.
->options($users, 'id', 'displayName()');Use grouped(...) when you want <optgroup> blocks.
Expected structure:
- each top-level item can expose
group - each top-level item must expose
list - every nested option should expose
valueandtext - nested options can optionally expose
attributes
Example:
$select = (new \Pair\Html\FormControls\Select('departmentId'))
->grouped([
(object) [
'group' => 'Sales',
'list' => [
// Every option needs value and text.
(object) ['value' => 10, 'text' => 'Inbound'],
(object) ['value' => 11, 'text' => 'Outbound'],
],
],
(object) [
'group' => 'Support',
'list' => [
(object) ['value' => 20, 'text' => 'Level 1'],
(object) ['value' => 21, 'text' => 'Level 2'],
],
],
])
->value(21);empty(...) prepends a blank option before the configured list.
- if you pass no text, Pair uses
Translator::do('SELECT_NULL_VALUE') - if the field is disabled or readonly, the inserted empty option is rendered with empty visible text
Example:
$status = (new \Pair\Html\FormControls\Select('status'))
->options([
'draft' => 'Draft',
'published' => 'Published',
])
// Show a first "no selection yet" option.
->empty('Select status')
->required();Important implementation detail:
-
render()prepends the empty option by mutating the internal list, so rendering the same instance multiple times afterempty()can duplicate that first option
multiple() only adds the HTML multiple attribute. For practical usage you usually combine it with:
- a name ending in
[], orarrayName() -
value([...])to preselect multiple items
Example:
$tags = (new \Pair\Html\FormControls\Select('tags[]'))
// multiple() affects HTML rendering only.
->multiple()
->options($allTags, 'id', 'label')
// Preselect multiple option values.
->value([3, 7, 12]);render() is one of the main reasons to document Select separately:
- it builds
<select ...> - it applies the
multipleattribute when enabled - it prepends the empty option when configured
- it renders flat
<option>items or nested<optgroup>structures - it marks selected options based on the current
value
Simple example:
$priority = (new \Pair\Html\FormControls\Select('priority'))
->label('Priority')
// A flat associative array is the most predictable validation case.
->options([
'low' => 'Low',
'normal' => 'Normal',
'high' => 'High',
])
->value('normal')
->class('form-select');
echo $priority->renderLabel();
echo $priority->render();Select::validate() overrides the base validation and adds list membership checks.
For the current implementation:
- if the field is required and the submitted value is empty, validation fails
- if the select has options, the submitted value must match one top-level option value
- if the field is optional, has an empty option, and the submitted value is empty, validation passes
- if the select has no options and is not required, validation passes
Example:
$status = (new \Pair\Html\FormControls\Select('status'))
// required() and options(...) cover the common CRUD case.
->required()
->options([
'draft' => 'Draft',
'published' => 'Published',
]);
// validate() checks Post::get('status') against the internal option list.
$isValid = $status->validate();The current validator checks only the top-level internal list and compares it against a single submitted value.
In practice this means:
- flat single-value selects are the safest and best-supported case
- grouped selects need extra attention because validation does not walk nested
optgroupoptions - true multi-select submissions (
tags[]) are not fully validated bySelect::validate()because the posted value is an array
If your form relies on grouped or multiple select values, add explicit validation in the model/request/controller layer.
-
hasOptions(): boolQuick helper to check whether the internal list is populated. -
value(...)Accepts arrays for preselection, unlike the baseFormControl::value(...). -
grouped(...)Useful when the UI needs semantic grouping, but keep the validation caveat above in mind.
$status = (new \Pair\Html\FormControls\Select('status'))
->label('Status')
// Keep a visible empty option before the business values.
->options([
'draft' => 'Draft',
'review' => 'In review',
'published' => 'Published',
])
->empty('Select status')
->required();$customer = (new \Pair\Html\FormControls\Select('customerId'))
->label('Customer')
// Pair uses object properties to build value/text.
->options($customers, 'id', 'companyName')
// Keep the current record selected while editing.
->value($order->customerId);$country = (new \Pair\Html\FormControls\Select('countryId'))
->label('Country')
// Each option can carry extra attributes generated from the source object.
->options($countries, 'id', 'name', ['iso2'])
->value(39);$tagSelect = (new \Pair\Html\FormControls\Select('tagIds[]'))
->label('Tags')
// multiple() changes the HTML widget, not the validator behavior.
->multiple()
->options($tags, 'id', 'label')
// The selected value can be preloaded as an array.
->value([1, 4, 9]);-
options(...)is the most common path and the best choice for normal CRUD selectors. -
empty()is useful for optional fields, but remember that it mutates the list during render. - If you need strict validation for grouped or multi-select inputs, do not rely on
Select::validate()alone.
See also: FormControl, Checkbox, Toggle, Collection, Form.