Skip to content

Commit

Permalink
Docs, form model (#45)
Browse files Browse the repository at this point in the history
* Sync with form changes

* Form model docs, start [skip ci]

* Apply fixes from StyleCI

* Fix

* Note [skip ci]

* Add info about nested form models

* Navigation

* Update docs/guide/en/form-model.md

Co-authored-by: Alexander Makarov <sam@rmcreative.ru>

* Use public properties

* Review fix

* Add separate section for nested form models

* Add separate section for nested form models 2

* Review fixes

---------

Co-authored-by: StyleCI Bot <bot@styleci.io>
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
  • Loading branch information
3 people authored Aug 11, 2024
1 parent 507e56e commit 60a7e20
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ TODO:

## Documentation

- [Guide](docs/guide/en/README.md)
- [Internals](docs/internals.md)

If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for
Expand Down
4 changes: 4 additions & 0 deletions docs/guide/en/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Documentation

- [Form model](form-model.md)
- [Nested form models](nested-form-models.md)
268 changes: 268 additions & 0 deletions docs/guide/en/form-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
# Form model

Form model is an abstraction over HTML forms. While you can use [forms](https://github.com/yiisoft/form) directly,
it can be more convenient to define form in object-oriented style. With this approach, form fields are defined as class
properties. Besides describing form data, form model also handles presentation and validation aspects.

To define a form model, create a class extending from `Yiisoft\FormModel\FormModel`.

## Properties

Form model properties are defined as class properties.

```php
final class LoginForm extends FormModel
{
public function __construct(
public ?string $login = null;
public ?string $password = null;
public bool $rememberMe = false;
) {
}
}
```

Example of working with properties individually:

```php
use Yiisoft\FormModel\FormModel;

/** @var FormModel $form */
$form = new LoginForm();
$form->login = 'john';

$form->hasProperty('login'); // true
$form->hasProperty('passwordConfirmation'); // false

echo $form->login; // "john"
// or
echo $form->getPropertyValue('login'); // "john"

echo $form->getPropertyValue('password'); // null
echo $form->getPropertyValue('passwordConfirmation'); // null
```

> Static properties are not included in form model's set of properties. On attempt to get such property's value,
> `Yiisoft\FormModel\Exception\StaticObjectPropertyException` exception will be thrown.
## Meta data

Form model's meta data includes:

- Property labels - the labels associated with each input (visible to the end user). When not set, they will be
automatically generated using inflector's
[`toWords()`](https://github.com/yiisoft/strings/blob/47a3675d817d98612aeec5083d73a184ecd5239c/src/Inflector.php#L495)
method from the [strings](https://github.com/yiisoft/strings) package.
- Property hints - complimentary text explaining certain details regarding each input. Optional, not shown when not set.
- Property placeholders - the values used as examples to help user fill the actual values. Optional, not shown when not
set.
- Form name - name of the form used to group all fields together when the data is submitted to the server. When not set,
the corresponding class name is used.

```php
use Yiisoft\FormModel\FormModel;

final class LoginForm extends FormModel
{
public function getPropertyLabels(): array
{
return [
'login' => 'Login',
'password' => 'Password',
'rememberMe' => 'Remember me',
];
}

public function getPropertyHints(): array
{
return [
'login' => 'ID or e-mail',
'password' => 'Case-sensitive',
];
}

public function getPropertyPlaceholders(): array
{
return [
'login' => 'john',
'password' => '123456',
];
}

public function getFormName(): string
{
return 'MyLoginForm';
}
}
```

### Translating meta data

If your application uses only one language, you can provide translations right in the mappings:

```php
use Yiisoft\FormModel\FormModel;
use Yiisoft\Translator\TranslatorInterface;
use Yiisoft\Validator\Rule\Email;
use Yiisoft\Validator\Rule\Length;

final class LoginForm extends FormModel implements AttributeTranslatorInterface
{
// ...

public function getPropertyLabels(): array
{
return [
'login' => 'Логин',
'password' => 'Пароль',
'rememberMe' => 'Запомнить меня',
];
}

public function getPropertyHints(): array
{
return [
'login' => 'ID или e-mail',
'password' => 'Зависит от регистра',
];
}

public function getPropertyPlaceholders(): array
{
return [
'login' => 'джон',
'password' => '123456',
];
}

// ...
}
```

For multiple languages, you can inject translator as a dependency and perform translation in the methods responsible
for getting single meta item:

```php
use Yiisoft\FormModel\FormModel;
use Yiisoft\Translator\TranslatorInterface;
use Yiisoft\Validator\Rule\Email;
use Yiisoft\Validator\Rule\Length;

final class LoginForm extends FormModel implements AttributeTranslatorInterface
{
// ...

public function __construct(private TranslatorInterface $translator)
{
}

public function getPropertyLabel(string $property): string
{
$label = parent::getPropertyLabel($property);

return $this->translator->translate($label);
}

public function getPropertyHint(string $property): string
{
$hint = parent::getPropertyHint($property);

return $this->translator->translate($hint);
}

public function getPropertyPlaceholder(string $propert): string
{
$placeholder = parent::getPropertyPlaceholder($property);

return $this->translator->translate($placeholder);
}

// ...
}
```

## Validation rules

Validation rules can be provided in the
[interface method implementation](https://github.com/yiisoft/validator/blob/master/docs/guide/en/using-validator.md#using-interface-method-implementation):

```php
use Yiisoft\FormModel\FormModel;
use Yiisoft\Validator\Rule\Email;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\RulesProviderInterface;

final class LoginForm extends FormModel implements RulesProviderInterface
{
public function getRules(): array
{
return [
'login' => $this->getLoginRules(),
'password' => $this->getPasswordRules(),
];
}

private function getLoginRules(): array
{
return [
new Required(),
new Length(min: 4, max: 40, lessThanMinMessage: 'Is too short.', greaterThanMaxMessage: 'Is too long.'),
new Email(),
];
}

private function getPasswordRules(): array
{
return [
new Required(),
new Length(min: 8, lessThanMinMessage: 'Is too short.'),
];
}
}
```

or via
[PHP attributes](https://github.com/yiisoft/validator/blob/master/docs/guide/en/configuring-rules-via-php-attributes.md).

```php
use Yiisoft\FormModel\FormModel;
use Yiisoft\Validator\Rule\Email;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Required;

final class LoginForm extends FormModel
{
#[Required]
#[Length(min: 4, max: 40, lessThanMinMessage: 'Is too short.', greaterThanMaxMessage: 'Is too long.')]
#[Email]
private ?string $login = null;
#[Required]
#[Length(min: 8, lessThanMinMessage: 'Is too short.')]
private ?string $password = null;

// ...
}
```

### `Safe` rule

This package also provides `Safe` rule that marks a model property as safe for filling from user input.

```php
use Yiisoft\FormModel\FormModel;
use Yiisoft\FormModel\Safe;
use Yiisoft\Validator\Rule\Email;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Required;

final class LoginForm extends FormModel
{
// ...

#[Safe]
private bool $rememberMe = false;

// ...
}
```
56 changes: 56 additions & 0 deletions docs/guide/en/nested-form-models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Nested form models

Form models can be nested.

- For one-to-one relations, you can add type hint of related class name to corresponding property.
- For one-to-many relation, use
[`Collection`](https://github.com/yiisoft/hydrator/blob/master/docs/guide/en/typecasting.md#collection) attribute from
[hydrator](https://github.com/yiisoft/hydrator) package. The class name of related collection must be specified as
parameter.

```php
use Yiisoft\FormModel\FormModel;
use Yiisoft\Validator\Rule\Each;
use Yiisoft\Validator\Rule\Integer;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Nested;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\Rule\Type\StringType;

final class PostCategory extends FormModel
{
public function __construct(
#[Required]
#[Length(max: 255)]
private string $name,
#[Collection(Post::class)]
#[Each([new Nested(Post::class)])]
private array $posts = [],
) {
}
}

final class Post extends FormModel
{
public function __construct(
#[Required]
#[Length(max: 255)]
private string $name,
#[StringType]
private string $description = '',
#[Required]
private Author $author,
) {
}
}

final class User extends FormModel
{
public function __construct(
#[Required]
#[Integer(min: 1)]
private int $id,
) {
}
}
```

0 comments on commit 60a7e20

Please sign in to comment.