PHPackages                             alexkart/typed-registry - 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. alexkart/typed-registry

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

alexkart/typed-registry
=======================

Dependency-free typed facade over mixed registries/config sources for PHPStan-level codebases.

0.1.0(8mo ago)1637—0%1MITPHPPHP &gt;=8.3

Since Oct 16Pushed 6mo agoCompare

[ Source](https://github.com/alexkart/typed-registry)[ Packagist](https://packagist.org/packages/alexkart/typed-registry)[ RSS](/packages/alexkart-typed-registry/feed)WikiDiscussions master Synced today

READMEChangelog (1)Dependencies (3)Versions (2)Used By (1)

typed-registry
==============

[](#typed-registry)

A dependency-free, strict facade that turns `mixed` config/registry values into real PHP types. No magic. No coercion. PHPStan-ready.

[![PHPStan Level](https://camo.githubusercontent.com/c47b2ba3238269b2d6f0e8c346934f4e94ee7ff7345c2738e62830182711717f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6d61782d626c75652e737667)](https://phpstan.org/)[![License: MIT](https://camo.githubusercontent.com/784362b26e4b3546254f1893e778ba64616e362bd6ac791991d2c9e880a3a64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

Why?
----

[](#why)

Modern PHP codebases use strict types and static analysis (PHPStan, Psalm), but configuration systems often return `mixed` values. This package provides a **strict, non-coercive boundary** between your config sources and typed code:

- **No implicit coercion** - `"123"` stays a string, won't become `int(123)`
- **Deep validation** - Lists and maps validate every element
- **Explicit defaults** - `getIntOr($key, 8080)` makes fallback behavior grep-able
- **Pluggable sources** - Wrap any config system via a simple `Provider` interface
- **PHPStan Level 10** - Zero errors, precise return types

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

[](#installation)

```
composer require alexkart/typed-registry
```

Requires PHP 8.3 or later.

Quick Start
-----------

[](#quick-start)

```
use TypedRegistry\TypedRegistry;
use TypedRegistry\ArrayProvider;

$registry = new TypedRegistry(new ArrayProvider([
    'app.debug' => true,
    'app.port' => 8080,
    'app.hosts' => ['web1.local', 'web2.local'],
]));

$debug = $registry->getBool('app.debug');         // bool(true)
$port  = $registry->getInt('app.port');           // int(8080)
$hosts = $registry->getStringList('app.hosts');   // list

// With defaults (no exception on missing/wrong type)
$timeout = $registry->getIntOr('app.timeout', 30); // int(30)
```

Core Concepts
-------------

[](#core-concepts)

### 1. Provider Interface

[](#1-provider-interface)

Any config source can be wrapped by implementing the `Provider` interface:

```
interface Provider
{
    public function get(string $key): mixed;
}
```

**Built-in providers:**

- **`ArrayProvider`** - Array-backed (great for tests or preloaded config)
- **`CallbackProvider`** - Wrap any callable
- **`CompositeProvider`** - Fallback chain (env → config → defaults)

### 2. Strict Type Checking

[](#2-strict-type-checking)

All `getXxx()` methods validate types **without coercion**:

```
$registry = new TypedRegistry(new ArrayProvider(['port' => '8080']));

$registry->getInt('port'); // ❌ Throws RegistryTypeError
// "[typed-registry] key 'port' must be int, got '8080'"
```

To handle this, either:

- Store the correct type: `['port' => 8080]`
- Use a default: `$registry->getIntOr('port', 8080)`

### 3. Collections

[](#3-collections)

Lists and maps are validated deeply:

```
// Lists (sequential arrays)
$registry->getStringList('app.hosts');  // ✅ ['a', 'b', 'c']
$registry->getIntList('app.ids');       // ✅ [1, 2, 3]

// Maps (associative arrays with string keys)
$registry->getStringMap('app.labels'); // ✅ ['env' => 'prod', 'tier' => 'web']
$registry->getIntMap('app.limits');    // ✅ ['max' => 100, 'min' => 10]

// Invalid examples
$registry->getStringList('key'); // ❌ If value is ['a', 123, 'c']
// "[typed-registry] key 'key[1]' must be string, got 123"

$registry->getStringMap('key'); // ❌ If value is [0 => 'value']
// "[typed-registry] key 'key' must be map, got array"
```

API Reference
-------------

[](#api-reference)

### Primitive Getters

[](#primitive-getters)

MethodReturn TypeThrows on Type Mismatch`getString(string $key)``string`✅`getInt(string $key)``int`✅`getBool(string $key)``bool`✅`getFloat(string $key)``float`✅### Nullable Variants

[](#nullable-variants)

Accept `null` as a legitimate value:

MethodReturn TypeThrows on Type Mismatch`getNullableString(string $key)``?string`✅ (unless null or string)`getNullableInt(string $key)``?int`✅ (unless null or int)`getNullableBool(string $key)``?bool`✅ (unless null or bool)`getNullableFloat(string $key)``?float`✅ (unless null or float)### Getters with Defaults

[](#getters-with-defaults)

Return the default value if key is missing or type mismatches (no exception):

MethodReturn TypeThrows`getStringOr(string $key, string $default)``string`❌`getIntOr(string $key, int $default)``int`❌`getBoolOr(string $key, bool $default)``bool`❌`getFloatOr(string $key, float $default)``float`❌### List Getters

[](#list-getters)

Return sequential arrays (validated with `array_is_list()`):

MethodReturn Type`getStringList(string $key)``list``getIntList(string $key)``list``getBoolList(string $key)``list``getFloatList(string $key)``list`### Map Getters

[](#map-getters)

Return associative arrays with string keys:

MethodReturn Type`getStringMap(string $key)``array``getIntMap(string $key)``array``getBoolMap(string $key)``array``getFloatMap(string $key)``array`Usage Examples
--------------

[](#usage-examples)

### Example 1: Wrap Any Config System

[](#example-1-wrap-any-config-system)

```
use TypedRegistry\TypedRegistry;
use TypedRegistry\Provider;

// Wrap your existing config library/registry
final class SomeExternalLibraryConfigProvider implements Provider
{
    public function get(string $key): mixed
    {
        // Adapt any existing config/registry system
        return \Some\Library\Config::get($key);
    }
}

$registry = new TypedRegistry(new SomeExternalLibraryConfigProvider());
$debug = $registry->getBool('app.debug');
$hosts = $registry->getStringList('app.allowed_hosts');
```

### Example 2: Composite Provider (Fallback Chain)

[](#example-2-composite-provider-fallback-chain)

Environment variables → Config file → Defaults:

```
use TypedRegistry\TypedRegistry;
use TypedRegistry\ArrayProvider;
use TypedRegistry\CallbackProvider;
use TypedRegistry\CompositeProvider;

$registry = new TypedRegistry(new CompositeProvider([
    new CallbackProvider(fn($k) => $_ENV[$k] ?? null),           // Environment
    new ArrayProvider(['app.port' => 8080, 'app.debug' => false]), // Config
    new ArrayProvider(['app.timeout' => 30]),                    // Defaults
]));

// Will use $_ENV['app.port'] if set, otherwise 8080 from config
$port = $registry->getInt('app.port');
```

### Example 3: Laravel Environment Variables with Type Casting

[](#example-3-laravel-environment-variables-with-type-casting)

Laravel's `Illuminate\Support\Env` class handles booleans (`"true"` → `true`) and nulls (`"null"` → `null`), but numeric strings remain strings (`"8080"` → `"8080"`). If you need automatic type casting for numeric environment variables, here's a custom provider:

```
use Illuminate\Support\Env;
use TypedRegistry\Provider;
use TypedRegistry\TypedRegistry;

final class EnvProvider implements Provider
{
    public function get(string $key): mixed
    {
        $value = Env::get($key);

        // If not a string, return as-is (booleans/nulls already handled by Env)
        if (!is_string($value)) {
            return $value;
        }

        // Only cast numeric strings
        if (!is_numeric($value)) {
            return $value;
        }

        // Cast to int if it represents a whole number
        if ((string) (int) $value === $value) {
            return (int) $value;
        }

        // Cast to float for decimals and scientific notation
        return (float) $value;
    }
}

// Usage
$env = new TypedRegistry(new EnvProvider());
$debug = $env->getBool('APP_DEBUG');      // "true" → bool(true)
$port = $env->getInt('APP_PORT');         // "8080" → int(8080)
$timeout = $env->getFloat('TIMEOUT');     // "2.5" → float(2.5)
$name = $env->getString('APP_NAME');      // "Laravel" → "Laravel"
```

> **Note:** This adapter performs type coercion, which differs from typed-registry's strict validation philosophy. Use it when you trust your environment variable format. Alternatively, the **[`alexkart/typed-registry-laravel`](https://github.com/alexkart/typed-registry-laravel)** package provides this and other Laravel-specific integrations out of the box, including `TypedEnv` and `TypedConfig` facades.

### Example 4: Testing with ArrayProvider

[](#example-4-testing-with-arrayprovider)

```
use PHPUnit\Framework\TestCase;
use TypedRegistry\TypedRegistry;
use TypedRegistry\ArrayProvider;

final class MyServiceTest extends TestCase
{
    public function testServiceUsesConfiguredPort(): void
    {
        $registry = new TypedRegistry(new ArrayProvider([
            'service.host' => 'localhost',
            'service.port' => 9000,
            'service.ssl' => true,
        ]));

        $service = new MyService($registry);

        self::assertSame('https://localhost:9000', $service->getBaseUrl());
    }
}
```

Error Handling
--------------

[](#error-handling)

When type validation fails, `RegistryTypeError` (extends `RuntimeException`) is thrown:

```
use TypedRegistry\RegistryTypeError;

try {
    $registry->getInt('app.port');
} catch (RegistryTypeError $e) {
    // Message format: "[typed-registry] key 'app.port' must be int, got '8080'"
    logger()->error($e->getMessage());
}
```

For graceful degradation, use the `getXxxOr()` variants:

```
$timeout = $registry->getIntOr('app.timeout', 30); // Never throws
```

Design Philosophy
-----------------

[](#design-philosophy)

### What This Library Does

[](#what-this-library-does)

- Provides strict type boundaries around `mixed` config sources
- Validates primitives, lists, and maps without coercion
- Enables PHPStan Level 10 compliance in config-heavy code
- Keeps implementation dependency-free (~250 LOC)

### What This Library Doesn't Do

[](#what-this-library-doesnt-do)

- **Coercion** - Use a dedicated validation library if you need `"123"` → `123`
- **Schema validation** - For DTOs/shapes, see future `typed-registry-psl` adapter
- **Config file parsing** - This library consumes already-loaded config
- **PSR container** - Not a service locator, strictly config/registry access

Development
-----------

[](#development)

```
# Install dependencies
composer install

# Run tests
composer test
# or: vendor/bin/phpunit

# Run static analysis
composer phpstan
# or: vendor/bin/phpstan analyse
```

**Quality Standards:**

- PHPStan Level: Max (10) with strict rules + bleeding edge
- Test Coverage: 100% (75 tests, 98 assertions)
- PHP Version: ≥8.3
- Dependencies: Zero (core package)

Ecosystem
---------

[](#ecosystem)

Optional packages that extend typed-registry:

- **[`alexkart/typed-registry-laravel`](https://github.com/alexkart/typed-registry-laravel)** ✅ **Available** - Laravel-specific providers (`EnvProvider` with type casting, `ConfigProvider`) plus `TypedEnv` and `TypedConfig` facades

Future packages (not required for core usage):

- **`alexkart/typed-registry-psl`** - Shape/union types via PHP Standard Library Types
- **`alexkart/typed-registry-schema`** - Schema validation and DTO mapping

Contributing
------------

[](#contributing)

Contributions are welcome! Please ensure:

1. All tests pass (`vendor/bin/phpunit`)
2. PHPStan Level 10 passes (`vendor/bin/phpstan analyse`)
3. Code follows existing style (strict types, explicit return types)

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

Credits
-------

[](#credits)

Maintained by the TypedRegistry contributors.

---

**Questions?** Open an issue on GitHub. **Need coercion?** Check out [webmozart/assert](https://github.com/webmozarts/assert) or [azjezz/psl](https://github.com/azjezz/psl).

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance64

Regular maintenance activity

Popularity19

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity40

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

Unknown

Total

1

Last Release

261d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/9946155ed284d2d4147cccc0c9e3fdeaf59310adf1145962d46f6a43820cb4c2?d=identicon)[alexkart](/maintainers/alexkart)

---

Top Contributors

[![alexkart](https://avatars.githubusercontent.com/u/8249105?v=4)](https://github.com/alexkart "alexkart (11 commits)")

---

Tags

adapterconfigdependency-freefacadephpphpstan-max-levelregistrystrictstrictly-typedtypeduniversalwrapperzero-dependencyPHPStanstatic analysisconfigstrictregistrytyped

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/alexkart-typed-registry/health.svg)

```
[![Health](https://phpackages.com/badges/alexkart-typed-registry/health.svg)](https://phpackages.com/packages/alexkart-typed-registry)
```

###  Alternatives

[php-stubs/wordpress-stubs

WordPress function and class declaration stubs for static analysis.

20416.0M415](/packages/php-stubs-wordpress-stubs)[php-stubs/woocommerce-stubs

WooCommerce function and class declaration stubs for static analysis.

953.5M104](/packages/php-stubs-woocommerce-stubs)[php-stubs/wp-cli-stubs

WP-CLI function and class declaration stubs for static analysis.

302.9M136](/packages/php-stubs-wp-cli-stubs)[php-stubs/wordpress-globals

Global variables and global constants from WordPress core.

14881.1k21](/packages/php-stubs-wordpress-globals)[selective/config

Config component, strictly typed

19194.8k3](/packages/selective-config)[selective/array-reader

A strictly typed array reader

1472.3k5](/packages/selective-array-reader)

PHPackages © 2026

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