PHPackages                             le0daniel/ztan - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. le0daniel/ztan

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

le0daniel/ztan
==============

PHPstan compatible library similar to zod

0.0.3(2mo ago)039↓37.5%PHPPHP ^8.5

Since Feb 24Pushed 2mo agoCompare

[ Source](https://github.com/le0daniel/ztan)[ Packagist](https://packagist.org/packages/le0daniel/ztan)[ RSS](/packages/le0daniel-ztan/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (10)Versions (4)Used By (0)

Ztan
====

[](#ztan)

Type-safe validation for PHP, inspired by [Zod](https://zod.dev). Parse unknown data, get fully typed results — with first-class [PHPStan](https://phpstan.org) support.

Why?
----

[](#why)

PHP arrays are not type-safe. Existing assertion libraries either require boilerplate or don't emit PHPStan types. Ztan gives you a single, chainable API to validate input data and get precise static types — no manual `@var` annotations, no guessing.

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

[](#requirements)

- PHP 8.5+
- PHPStan 2.1+ (for static analysis)

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

[](#installation)

```
composer require le0daniel/ztan
```

Add the PHPStan extension to your `phpstan.neon`:

```
includes:
    - vendor/le0daniel/ztan/extension.neon
```

Quick Example
-------------

[](#quick-example)

```
use Le0daniel\Ztan\Ztan;

$userSchema = Ztan::arrayShape([
    'name'  => Ztan::string()->trim()->notEmpty(),
    'email' => Ztan::string()->email(),
    'age'   => Ztan::int()->gte(0)->lte(150),
    'role?' => Ztan::enum(Role::class),
]);

// Parse — throws ValidationException on failure
$user = $userSchema->parse($input);

// PHPStan knows: array{name: string, email: string, age: int, role?: Role}
$user['name']; // string ✓
```

API
---

[](#api)

### Scalars

[](#scalars)

```
Ztan::string()    // StringType
Ztan::int()       // IntType
Ztan::float()     // FloatType
Ztan::bool()      // BoolType
Ztan::null()      // NullType
Ztan::mixed()     // MixedType
Ztan::never()     // NeverType
Ztan::literal('active')         // LiteralType
Ztan::enum(Status::class)       // EnumType
Ztan::instance(DateTime::class) // InstanceType
Ztan::dateTimeString('Y-m-d')   // DateTimeStringType
```

### Collections

[](#collections)

```
Ztan::list(Ztan::int())                // list
Ztan::record(Ztan::string())           // array
Ztan::tuple(Ztan::string(), Ztan::int()) // array{string, int}
Ztan::arrayShape([...])                // array{key: type, ...}
Ztan::objectShape([...])               // object{key: type, ...}
```

### Unions

[](#unions)

```
Ztan::union(Ztan::string(), Ztan::int())  // string|int

Ztan::discriminatedUnion('type', [
    Ztan::arrayShape(['type' => Ztan::literal('a'), 'value' => Ztan::string()]),
    Ztan::arrayShape(['type' => Ztan::literal('b'), 'count' => Ztan::int()]),
])
```

### String Constraints

[](#string-constraints)

Order matters. Constraints are applied in the order they are defined.

```
Ztan::string()
    ->trim()
    ->lowercase()
    ->uppercase()
    ->minLength(1)
    ->maxLength(255)
    ->startsWith('prefix')
    ->endsWith('suffix')
    ->regex('/^[a-z]+$/')
    ->notEmpty()
    ->email()
    ->url()
    ->webUrl()
```

### Number Constraints

[](#number-constraints)

Order matters. Constraints are applied in the order they are defined.

```
Ztan::int()
    ->gt(0)->gte(0)
    ->lt(100)->lte(100)
    ->range(0, 100)
    ->positive()->negative()
    ->nonnegative()->nonpositive()
    ->multipleOf(5)

// Same for Ztan::float() (except multipleOf)
```

### Collection Constraints

[](#collection-constraints)

Order matters. Constraints are applied in the order they are defined.

```
Ztan::list(Ztan::string())
    ->minItems(1)->maxItems(10)
    ->nonEmpty()
    ->length(5)

Ztan::record(Ztan::int())
    ->minProperties(1)->maxProperties(10)
    ->nonEmpty()
```

### DateTime Constraints

[](#datetime-constraints)

Order matters. Constraints are applied in the order they are defined.

```
Ztan::dateTimeString('Y-m-d')
    ->after(new DateTime('2020-01-01'))
    ->before(new DateTime('2030-01-01'))
    ->between($start, $end)
    ->past()
    ->future()
```

### Enum Constraints

[](#enum-constraints)

Order matters. Constraints are applied in the order they are defined.

```
Ztan::enum(Role::class)
    ->only([Role::Admin, Role::User])
    ->not([Role::Guest])
```

### Modifiers

[](#modifiers)

```
// Nullable — accepts null or the inner type
Ztan::string()->nullable()  // string|null

// Default on failure
Ztan::string()->catch('fallback')

// Transform the validated value
Ztan::string()->transform(fn(string $v): int => strlen($v))

// Custom validation
Ztan::string()->refine(fn(string $v): bool => str_contains($v, '@'), 'Must contain @')

// Preprocess before validation
Ztan::int()->preprocess(fn(mixed $v): mixed => is_string($v) ? (int) $v : $v)
```

### Coercion

[](#coercion)

Automatically coerce values before validation:

```
Ztan::coerce()->string()  // int, float, bool → string
Ztan::coerce()->int()     // float, bool, numeric string → int
Ztan::coerce()->float()   // int, bool, numeric string → float
Ztan::coerce()->bool()    // 1/0, 'true'/'false' → bool
Ztan::coerce()->enum(Status::class) // string/int → enum case
```

### Optional Properties

[](#optional-properties)

Suffix the key with `?`:

```
Ztan::arrayShape([
    'name'    => Ztan::string(),
    'middle?' => Ztan::string(), // optional
])
```

Parsing
-------

[](#parsing)

```
// Throws ValidationException
$value = $schema->parse($input);

// Returns ParseSuccess or ParseError
$result = $schema->safeParse($input);

if ($result instanceof \Le0daniel\Ztan\Data\ParseError) {
    foreach ($result->issues as $issue) {
        echo $issue->getPathAsString() . ': ' . $issue->message;
    }
} else {
    $result->data; // validated value
}
```

PHPStan
-------

[](#phpstan)

Ztan ships with a PHPStan extension that infers precise return types from your schemas — no manual annotations needed. After including `extension.neon`, calls to `->parse()` and `->safeParse()` are fully typed.

```
$schema = Ztan::arrayShape([
    'id'   => Ztan::int(),
    'tags' => Ztan::list(Ztan::string()),
]);

$data = $schema->parse($input);
// PHPStan infers: array{id: int, tags: list}
```

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance85

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 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.

###  Release Activity

Cadence

Every ~2 days

Total

3

Last Release

77d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/22875367?v=4)[Leo Daniel A](/maintainers/leodaniel)[@leodaniel](https://github.com/leodaniel)

---

Top Contributors

[![le0daniel](https://avatars.githubusercontent.com/u/11374766?v=4)](https://github.com/le0daniel "le0daniel (34 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/le0daniel-ztan/health.svg)

```
[![Health](https://phpackages.com/badges/le0daniel-ztan/health.svg)](https://phpackages.com/packages/le0daniel-ztan)
```

###  Alternatives

[lcobucci/clock

Yet another clock abstraction

796190.9M114](/packages/lcobucci-clock)[symfony/clock

Decouples applications from the system clock

431168.9M205](/packages/symfony-clock)[eventsauce/eventsauce

A pragmatic event sourcing library for PHP with a focus on developer experience.

8632.1M47](/packages/eventsauce-eventsauce)[ecotone/ecotone

Supporting you in building DDD, CQRS, Event Sourcing applications with ease.

558549.8k17](/packages/ecotone-ecotone)[mcp/sdk

Model Context Protocol SDK for Client and Server applications in PHP

1.4k423.9k30](/packages/mcp-sdk)[flow-php/etl

PHP ETL - Extract Transform Load - Abstraction

374468.4k51](/packages/flow-php-etl)

PHPackages © 2026

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