Skip to content

Commit

Permalink
add some documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
tflori committed Nov 2, 2017
1 parent 3df9be0 commit de12ce8
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 7 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,29 @@ composer require tflori/verja
Initialize a container, set the input data, define filters and validators, validate the data, get the data.

```php
$container = new Verja\Gate();
$container->addFields([
<?php
$gate = new Verja\Gate();
$gate->addFields([
'username' => ['notEmpty', 'strLen(3, 20)'],
'password' => ['notEmpty', 'strLen(8)'],
'email' => ['notEmpty', 'email'],
]);

if ($container->validate($_POST)) {
if ($gate->validate($_POST)) {
// how ever your orm works..
$user = new User($container->getData());
$user = new User($gate->getData());
$user->save();
} else {
$errors = $container->getErrors();
$errors = $gate->getErrors();
}
```

If you prefer auto completion you can of course pass objects:

```php
$container->addFields([

<?php
$gate->addFields([
'username' => (new Field())
->addValidator(new Validator\NotEmpty())
->addValidator(new Validator\StringLength(3, 20)),
Expand Down
216 changes: 215 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,228 @@ froward.
### Initialize

Usually you need validation and filtering in controllers where you control the application flow and pass user (or third
party) input to you business models.
party) input to your business models. To test these controllers there are now two options:

1. Test with edge cases (a lot of different input value combinations)
2. Test that the expected validators get created and used.

We suggest the second method. So put your `new Verja\Gate()` to a factory and replace this factory to mock this object
in tests.

```php?start_inline=true
use DependencyInjector\DI;
use Verja\Gate;
DI::set('verja', function() {
return new Gate();
}, false);
function controller() {
/** @var Gate $gate */
$gate = DI::get('verja');
}
```

> Note: we are using `tflori\dependency-injector` here. The third parameter means that the object should not be cached.
> Instead every time a new `Gate` will be initialized.
### Define Fields

A `Field` can be initialized as a usual object and passed to `Gate` with `Gate::addField()`. You can also pass an
array of fields to `Gate::addFields()`. These methods are literally aliased by `Gate::accept()` and `Gate::accepts()`.

> Note: The `Gate` will not let any input pass that is not allowed to pass. That means that you also have to define
> fields that you don't want to validate or filter.
These methods also allow the short form of defining fields to write code much easier and faster. So the following
calls are equal and allow any value for the key `'comment'`:

```php?start_inline=true
use Verja\Gate;
use Verja\Field;
$gate = new Gate();
$gate->accept('comment');
$gate->accept('comment', []);
$gate->accept('comment', new Field());
$gate->accepts(['comment']);
$gate->accepts(['comment' => []]);
$gate->accepts(['comment' => new Field()]);
```

> Note: the later calls just overwrite the comment definition.
#### Add Filters To Field

You can add any object of a class that is implementing `Verja\FilterInterface` to a `Field` with `Field::addFilter()`.
Because the order might be significant you can also prepend a filter with either `Field::prependFilter()` or the
parameter `$prepend` set to `true`.

The methods to add a filter to an field also allow `$filter` to be a string or a callable. To pass a callable is a
shortcut for passing `new \Verja\Filter\Callback($callable)`. A string will be converted to a filter with
`\Verja\Filter::fromString()` which is searching in LIFO order (last in first out) in all registered namespaces for a
matching filter and colons divide the parameters.

Filters can also be passed to the constructor of `Field` in an array where the order is maintained.

```php?start_inline=true
use Verja\Field;
use Verja\Filter;
$field = new Field([new Filter\Trim('/')]); // trim slashes
$field->prependFilter('trim'); // first trim whitespace
$field->appendFilter('replace:foo:bar'); // after trim whitespace and trim slashes replace foo with bar
$field->addFilter(function ($value) {
return substr($value, 0, 3); // get the first 3 chars of the result
});
```

#### Add Validators To Field

The identical methods exists for the `Verja\ValidatorInterface`. The order is also maintained and is relevant when you
try to get value from a field that is not valid. In this case the error message of the first validator that fails is
in the exception message.

The converter method `\Verja\Validator::fromString()` allows an exclamation mark in front of the validator name that
will invert the validator.

```php?start_inline=true
use Verja\Field;
use Verja\Validator;
use DependencyInjector\DI;
$field = new Field([new Validator\Contains('@')]); // validate that it contains an @
$field->prependValidator('notEmpty'); // prepend not empty check
$field->appendValidator('emailAddress'); // append that it is an email address
$field->addValidator(function ($value) { // validate that the email is unknown
if (DI::get('db')->select('user')->where('email', $value)->count()) {
return Validator::buildError('EMAIL_TAKEN', $value, 'Email address already taken');
}
return true;
});
```

#### Required Fields

Fields can be required which means that an error leads to an exception when you try to get data for this field and the
value is validated even if it is empty. On the other hand it means that a required field is ok to be empty if no
no validator is defined.

The required attribute can be set via `Field::required()` or the string `'required'` in the array of definitions.

```php?start_inline=true
use Verja\Field;
$field = new Field([ 'required', 'notEmpty' ]);
```

### Filter And Validate

As there is no magic you can use filters and validators directly or directly use a field for combined filters and
validators for a specific value. And last but not least you can pass multiple values to the gate to validate and filter
all of them at once.

```php?start_inline=true
use Verja\Gate;
use Verja\Field;
use Verja\Validator;
use Verja\Filter;
$validator = new Validator\Contains('foo');
var_dump($validator->validate('some foo may happen')); // true
$filter = new Filter\Trim('/');
var_dump($filter->filter('/relative/path')); // "relative/path"
$field = new Field(['trim:/ ', 'notEmpty']);
var_dump($filtered = $field->filter(' /relative/path /')); // "relative/path"
var_dump($field->validate($filtered)); // true
$gate = new Gate(['foo' => 'from constructor']);
$gate->accepts([
'foo' => [ 'contains:bar' ],
'pw' => [ 'required', 'strLen:3', 'equals:pw_conf' ],
]);
var_dump($gate->validate()); // false (no pw given, foo does not contain bar)
var_dump($gate->validate(['foo' => 'bar', 'pw' => 'abc', 'pw_conf' => 'abc'])); // true
$gate->setData(['foo' => 'bar', 'pw' => '123', 'pw_conf' => 'abc']);
$gate->getData(); // throws "Invalid pw: value should be equal to contexts pw_conf"
```

### Show Errors

The `Validator` may contain an error in the following format after validating an invalid value:

```php?start_inline=true
return [
'key' => 'NOT_CONTAINS', // a key that can be used for translation
'value' => 'any string', // the value that got validated
'message' => 'value should contain "bar"', // OPTIONAL - a default error message (used for exceptions)
'parameters' => [ 'subString' => 'bar' ], // OPTIONAL - parameters used for validation
];
```

The `Field` contains an array of all errors occurred during `Field::validate()` and the `Gate` contains an array with
all arrays of errors from the fields. The method `Gate::getErrors()` may return something like this:

```php?start_inline=true
return [
'foo' => [
[
'key' => 'NOT_CONTAINS',
'value' => 'any string',
'message' => 'value should contain "bar"',
'parameters' => [ 'subString' => 'bar' ],
]
],
'pw' => [
[
'key' => 'STRLEN_TOO_SHORT',
'value' => 'abc123',
'message' => 'value should be at least 8 characters long',
'parameters' => [ 'min' => 8, 'max' => 0 ],
],
[
'key' => 'NOT_EQUAL',
'value' => 'abc123',
'message' => 'value should be equal to contexts pw_conf',
'parameters' => [ 'opposite' => 'pw_conf', 'jsonEncode' => true ]
]
],
];
```

### Example

This is a basic real world example:

```php?start_inline=true
use Verja\Gate;
use Verja\Validator;
// imagine a class that persists somewhere somehow (maybe ORM\Entity from tflori\orm?)
use App\Model\User;
// imagine a dependency injection system (maybe tflori/dependency-injector?)
use DependencyInjector\DI;
$gate = new Gate();
$gate->accepts([
'username' => [ 'required', 'trim', 'strLen:3:20', new Validator\Callback(function ($value) {
return DI::get('em')->fetch(User::class)->where('username', $value)->count() ?
Validator::buildError('USERNAME_TAKEN', $value, 'value should be unique in user.username') : true;
})],
'password' => [ 'required', 'strLen:8', 'equals:password_confirmation' ],
'email' => [ 'required', 'trim', 'emailAddress' ],
]);
if ($_SERVER['REQUEST_METHOD'] === 'post' && $gate->validate($_POST)) {
$user = new User;
$user->username = $gate->get('username');
$user->password = password_hash($gate->get('password'), PASSWORD_BCRYPT);
$user->email = $gate->get('email');
$user->save();
}
```

0 comments on commit de12ce8

Please sign in to comment.