PHPackages                             tihloh/prefab-input - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. tihloh/prefab-input

ActiveLibrary

tihloh/prefab-input
===================

Framework-independent input validation, normalization, casting, wildcard arrays and multipart upload handling for Prefab PHP.

00PHP

Since Aug 24Pushed todayCompare

[ Source](https://github.com/tihloh/prefab-input)[ Packagist](https://packagist.org/packages/tihloh/prefab-input)[ RSS](/packages/tihloh-prefab-input/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Prefab Input
============

[](#prefab-input)

**Prefab Input** turns raw/untrusted PHP input into clean, normalized, typed, validated and deeply whitelisted application data.

> Define exactly what your application accepts. Everything else stays outside the validated result.

It combines closely related input-boundary concerns:

- validation;
- trimming and normalization;
- type casting;
- safe field whitelisting;
- defaults and conditional rules;
- nested arrays and wildcard paths;
- multiple/repeated form rows;
- `$_FILES` normalization;
- multipart file validation;
- JSON request bodies;
- custom rules and transformers;
- friendly validation errors.

Prefab Input is standalone. It does not require Routes, Database, Users, Auth, Permissions, Logs, Laravel, or another framework.

Requirements
------------

[](#requirements)

- PHP 8.1 or newer
- `ext-fileinfo` for secure MIME detection
- Composer when installed as a package

Installation
------------

[](#installation)

When published:

```
composer require tihloh/prefab-input
```

---

1. Quick start
==============

[](#1-quick-start)

```
use Tihloh\Prefab\Input\Input;

$result = Input::from($_POST)->process([
    'name' => 'trim|required|string|max:100',
    'email' => 'trim|lowercase|required|email',
    'age' => 'nullable|integer|min:18',
    'active' => 'default:true|boolean',
]);

if ($result->fails()) {
    print_r($result->errors());
    return;
}

$data = $result->validated();
```

`validated()` contains only schema-declared fields that passed processing.

---

2. One input pipeline
=====================

[](#2-one-input-pipeline)

Incoming values often need several steps before business logic:

```
"  Christian  "   → "Christian"
"USER@MAIL.COM"   → "user@mail.com"
"25"              → 25
"true"            → true
""                → null

```

Prefab Input handles this as one pipeline:

```
raw input
   ↓
whitelist/schema
   ↓
normalize
   ↓
cast
   ↓
validate
   ↓
safe validated data

```

---

3. Schema as a whitelist
========================

[](#3-schema-as-a-whitelist)

Suppose a client sends:

```
[
    'name' => 'Christian',
    'email' => 'user@example.com',
    'role' => 'admin',
    'is_superuser' => true,
]
```

but the schema declares only:

```
$result = Input::from($data)->process([
    'name' => 'trim|required|string',
    'email' => 'trim|lowercase|required|email',
]);
```

Then:

```
$result->validated();
```

returns only:

```
[
    'name' => 'Christian',
    'email' => 'user@example.com',
]
```

Undeclared client fields never become validated application data.

---

4. Compact and array schemas
============================

[](#4-compact-and-array-schemas)

Compact syntax:

```
'name' => 'trim|required|string|max:100'
```

Array syntax:

```
'name' => [
    'trim',
    'required',
    'string',
    'max:100',
]
```

Array schemas can also contain callable validation rules.

---

5. Transformations and casting
==============================

[](#5-transformations-and-casting)

Built-in operations include:

OperationEffect`trim`Trim string whitespace`lowercase`Lowercase a string`uppercase`Uppercase a string`null_if_empty`Convert `''` to `null``string`Cast sensible scalar/stringable values`integer`Cast a valid integer representation`float`Cast numeric input to float`boolean`Cast common boolean forms`array`Keep arrays or wrap a scalar in an arrayExample:

```
$result = Input::from([
    'email' => '  ADMIN@EXAMPLE.COM ',
    'age' => '35',
    'active' => 'true',
])->process([
    'email' => 'trim|lowercase|email',
    'age' => 'integer|min:18',
    'active' => 'boolean',
]);
```

Validated output:

```
[
    'email' => 'admin@example.com',
    'age' => 35,
    'active' => true,
]
```

Invalid casts produce validation errors rather than silently pretending the requested type was produced.

---

6. Core validation rules
========================

[](#6-core-validation-rules)

Built-in rules include:

```
required
nullable
sometimes
required_if
required_with
email
url
string
integer
float
numeric
boolean
array
date
min
max
between
in
not_in
same
different
regex
confirmed
distinct
file
image
mimes
mimetypes
min_size
max_size
dimensions

```

---

7. Required, nullable, sometimes and defaults
=============================================

[](#7-required-nullable-sometimes-and-defaults)

```
'name' => 'required|string'
'middle_name' => 'nullable|trim|string|max:100'
'bio' => 'sometimes|trim|string|max:500'
'active' => 'default:true|boolean'
'page' => 'default:1|integer|min:1'
```

`nullable` accepts an absent/null/empty value as `null`. `sometimes` ignores an absent field completely. `default` applies only when the field is absent.

---

8. Conditional validation
=========================

[](#8-conditional-validation)

```
'company_name' => 'required_if:type,business|trim|string'
'phone_extension' => 'required_with:phone|string'
```

These rules also work inside wildcard rows when the referenced rule path uses the same wildcard positions.

Example:

```
'items.*.type' => 'required|in:product,service',
'items.*.sku' => 'required_if:items.*.type,product|string',
```

For `items.3.sku`, Prefab compares `items.3.type`.

---

9. Nested input
===============

[](#9-nested-input)

Dot notation reads and writes nested arrays:

```
$result = Input::from([
    'user' => [
        'name' => ' Christian ',
        'email' => 'USER@EXAMPLE.COM',
    ],
])->process([
    'user.name' => 'trim|required|string',
    'user.email' => 'trim|lowercase|required|email',
]);
```

Validated output:

```
[
    'user' => [
        'name' => 'Christian',
        'email' => 'user@example.com',
    ],
]
```

---

10. Array form inputs
=====================

[](#10-array-form-inputs)

PHP automatically converts HTML names such as:

```

```

into:

```
[
    'tags' => ['PHP', 'Go'],
]
```

Process each item using `*`:

```
$result = Input::from($_POST)->process([
    'tags' => 'required|array|max:10|distinct',
    'tags.*' => 'trim|required|string',
]);
```

---

11. Repeated/nested rows with wildcards
=======================================

[](#11-repeatednested-rows-with-wildcards)

HTML:

```

```

PHP produces:

```
[
    'items' => [
        ['product_id' => '10', 'qty' => '2'],
        ['product_id' => '15', 'qty' => '3'],
    ],
]
```

Prefab schema:

```
$result = Input::from($_POST)->process([
    'items' => 'required|array|max:20',
    'items.*.product_id' => 'required|integer',
    'items.*.qty' => 'required|integer|min:1',
]);
```

Validated output:

```
[
    'items' => [
        ['product_id' => 10, 'qty' => 2],
        ['product_id' => 15, 'qty' => 3],
    ],
]
```

Errors keep their concrete index:

```
[
    'items.1.qty' => [
        'The items 1 qty field must be at least 1.',
    ],
]
```

Nested wildcards are also supported:

```
'departments.*.employees.*.email' => 'trim|lowercase|required|email'
```

---

12. Deep whitelisting
=====================

[](#12-deep-whitelisting)

Wildcard schemas do not copy undeclared fields from parent arrays.

Input:

```
[
    'items' => [
        [
            'product_id' => 10,
            'qty' => 2,
            'price' => 0,
            'is_free' => true,
        ],
    ],
]
```

Schema:

```
[
    'items' => 'array',
    'items.*.product_id' => 'integer',
    'items.*.qty' => 'integer',
]
```

Validated output:

```
[
    'items' => [
        [
            'product_id' => 10,
            'qty' => 2,
        ],
    ],
]
```

This makes nested client input safer to pass to application services or persistence.

---

13. Standard multipart forms
============================

[](#13-standard-multipart-forms)

For normal PHP `multipart/form-data` requests, PHP already parses the protocol into `$_POST` and `$_FILES`.

Use:

```
$input = Input::fromRequest();
```

or explicitly:

```
$input = Input::from($_POST, $_FILES);
```

HTML:

```

    Save

```

Process it naturally:

```
$result = Input::fromRequest()->process([
    'title' => 'trim|required|string|max:200',
    'document' => 'required|file|mimes:pdf|max_size:20mb',
]);
```

Prefab does not reimplement the standard PHP multipart boundary parser. It normalizes PHP's already-parsed request data.

---

14. UploadedFile
================

[](#14-uploadedfile)

Uploaded files become `UploadedFile` objects:

```
use Tihloh\Prefab\Input\UploadedFile;

$file = $result->validated('document');

if ($file instanceof UploadedFile) {
    $file->name();
    $file->tmpPath();
    $file->size();
    $file->extension();
    $file->mime();
    $file->error();
    $file->isValid();
    $file->isImage();
    $file->dimensions();
}
```

`mime()` detects MIME from temporary file contents using `fileinfo`; it does not simply trust the browser-provided MIME string.

`UploadedFile` represents temporary input only. Permanent storage is intentionally outside Prefab Input.

---

15. File rules
==============

[](#15-file-rules)

Examples:

```
'photo' => 'required|file|image|max_size:5mb'
'document' => 'required|file|mimes:pdf,doc,docx|max_size:20mb'
'avatar' => 'image|mimetypes:image/jpeg,image/png'
```

Rules:

RuleMeaning`file`Valid PHP upload`image`Upload is a readable image`mimes:pdf,png`Allowed extension plus detected MIME for supported formats`mimetypes:application/pdf,image/png`Allowed detected MIME`min_size:1kb`Minimum byte size`max_size:10mb`Maximum byte size`dimensions:min_width=200,min_height=200`Image dimension limitsSupported size suffixes are `b`, `kb`, `mb`, and `gb`.

Dimension examples:

```
'photo' => 'image|dimensions:min_width=200,min_height=200,max_width=4000,max_height=4000'
```

---

16. Multiple file uploads
=========================

[](#16-multiple-file-uploads)

HTML:

```

```

Prefab normalizes PHP's column-oriented `$_FILES` representation into:

```
[
    'attachments' => [
        UploadedFile,
        UploadedFile,
    ],
]
```

Then:

```
$result = Input::fromRequest()->process([
    'attachments' => 'array|max:10',
    'attachments.*' => 'file|max_size:10mb',
]);
```

---

17. Nested multipart rows
=========================

[](#17-nested-multipart-rows)

HTML can mix ordinary fields and files:

```

```

Schema:

```
$result = Input::fromRequest()->process([
    'employees' => 'required|array',
    'employees.*.name' => 'trim|required|string|max:100',
    'employees.*.photo' => 'nullable|image|max_size:5mb',
]);
```

`$_POST` and `$_FILES` are merged into one logical nested input tree before validation.

---

18. JSON requests
=================

[](#18-json-requests)

`Input::fromRequest()` detects `Content-Type: application/json` and reads `php://input` automatically:

```
$result = Input::fromRequest()->process([
    'title' => 'trim|required|string|max:200',
    'priority' => 'default:0|integer|between:0,5',
    'tags' => 'sometimes|array|max:10',
    'tags.*' => 'trim|string',
]);
```

Invalid JSON produces a clear exception instead of silently converting the request into empty data.

Raw multipart parsing for unusual non-standard request flows can later belong to an HTTP transport layer; Prefab Input remains focused on input normalization and validation.

---

19. Result object
=================

[](#19-result-object)

`process()` returns `InputResult`:

```
$result->passes();
$result->fails();
$result->raw();
$result->all();
$result->validated();
$result->validated('document');
$result->errors();
$result->first();
$result->first('email');
$result->value('user.email');
```

Meaning:

```
raw()       → original normalized request tree before schema processing
all()       → declared fields after transformations, including invalid values
validated() → only declared fields that passed processing
errors()    → field-indexed validation messages

```

Prefer `validated()` when passing request data into application services.

---

20. Friendly attributes and messages
====================================

[](#20-friendly-attributes-and-messages)

```
$input->attributes([
    'email_address' => 'Email address',
    'items.*.qty' => 'Item quantity',
]);

$input->messages([
    'email.required' => 'Please enter your email address.',
    'items.*.qty.min' => ':attribute must be at least :value.',
]);
```

Wildcard pattern messages/attributes apply to their concrete paths unless an even more specific concrete key is configured.

---

21. Custom rules
================

[](#21-custom-rules)

```
$input->rule('even', function ($field, $value) {
    return is_int($value) && $value % 2 === 0
        ? null
        : 'The value must be an even integer.';
});

$result = $input->process([
    'quantity' => 'integer|even',
]);
```

Custom rule callbacks receive field, current value, raw data, parsed parameters and existence state.

This is also the extension point for database-aware `unique`/`exists` behavior without making Prefab Database mandatory.

---

22. Inline callable rules
=========================

[](#22-inline-callable-rules)

```
$result = $input->process([
    'username' => [
        'trim',
        'required',
        function ($field, $value) {
            return str_contains((string) $value, ' ')
                ? 'Username cannot contain spaces.'
                : null;
        },
    ],
]);
```

Return `null` when valid, or a non-empty error string when invalid.

---

23. Custom transformations
==========================

[](#23-custom-transformations)

```
$input->transform('phone', function ($value) {
    return preg_replace('/[^0-9+]/', '', (string) $value);
});

$result = $input->process([
    'phone' => 'trim|phone|required',
]);
```

Custom transformers receive the current value, parameters, field name and raw data.

---

24. Database-aware rules without a hard dependency
==================================================

[](#24-database-aware-rules-without-a-hard-dependency)

```
$input->rule('unique_email', function ($field, $value) use ($users) {
    return $users->findByEmail((string) $value) === null
        ? null
        : 'That email address is already in use.';
});

$result = $input->process([
    'email' => 'trim|lowercase|required|email|unique_email',
]);
```

A future Database adapter may provide reusable resolvers while the Input core stays database-independent.

---

25. Responsibility boundary
===========================

[](#25-responsibility-boundary)

Prefab Input owns the incoming-data boundary:

```
Browser / API
      ↓
POST / JSON / multipart
      ↓
Prefab Input
├── normalize
├── cast
├── validate
├── file inspection
└── whitelist
      ↓
application-safe data

```

It deliberately does **not** own permanent file storage, directories, cloud storage, public URLs, database persistence, HTTP responses, or routing.

A future file-storage package could consume `UploadedFile` without changing Input itself.

---

26. API quick reference
=======================

[](#26-api-quick-reference)

APIPurpose`Input::from($data, $files = [])`Build from explicit arrays`Input::fromRequest()`Read normal PHP form/multipart or JSON request data`process()`Normalize/cast/validate/whitelist using a schema`rule()`Register a custom validation rule`transform()`Register a custom transformer`attributes()`Configure friendly field labels`messages()`Configure validation messages`Input::normalizeFiles()`Normalize PHP `$_FILES` manually`InputResult`:

APIPurpose`passes()` / `fails()`Validation status`raw()`Original input tree`all()`Processed declared fields`validated()`Safe validated output or one validated path`errors()`Error bag`first()`First error globally or for a field`value()`Read a processed value by dot path`UploadedFile`:

APIPurpose`name()`Original client filename`tmpPath()`PHP temporary path`size()`File size in bytes`extension()`Original filename extension`mime()`Content-detected MIME`error()`PHP upload error code`isValid()`Valid temporary upload state`isImage()`Detect readable image`dimensions()`Image width/height---

27. Design philosophy
=====================

[](#27-design-philosophy)

Prefab Input stays simple for ordinary forms:

```
$result = Input::from($_POST)->process([
    'name' => 'trim|required',
]);
```

The same API scales to large dynamic forms and APIs:

```
$result = Input::fromRequest()->process([
    'departments.*.employees.*.email' => 'trim|lowercase|required|email',
    'departments.*.employees.*.photo' => 'nullable|image|max_size:5mb',
]);
```

The core principle is: **turn untrusted external data into predictable application data without forcing a framework or persistence layer.**

###  Health Score

20

↑

LowBetter than 12% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/6ec00222b22ba37eb69aff9973c5303fe5773cb4c7016bf0e7aae09fe9edb232?d=identicon)[tihloh](/maintainers/tihloh)

---

Top Contributors

[![tihloh](https://avatars.githubusercontent.com/u/8960509?v=4)](https://github.com/tihloh "tihloh (12 commits)")

### Embed Badge

![Health badge](/badges/tihloh-prefab-input/health.svg)

```
[![Health](https://phpackages.com/badges/tihloh-prefab-input/health.svg)](https://phpackages.com/packages/tihloh-prefab-input)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
