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

ActiveLibrary

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

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

0.1.0(6mo ago)1384↓50%1MITPHPPHP &gt;=8.3

Since Oct 16Pushed 4mo 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 1mo ago

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

36

—

LowBetter than 82% of packages

Maintenance71

Regular maintenance activity

Popularity18

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

208d 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

[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k43.5M5.2k](/packages/larastan-larastan)[szepeviktor/phpstan-wordpress

WordPress extensions for PHPStan

3257.8M898](/packages/szepeviktor-phpstan-wordpress)[ekino/phpstan-banned-code

Detected banned code using PHPStan

2925.6M92](/packages/ekino-phpstan-banned-code)[php-stubs/wordpress-stubs

WordPress function and class declaration stubs for static analysis.

19013.0M263](/packages/php-stubs-wordpress-stubs)[shipmonk/dead-code-detector

Dead code detector to find unused PHP code via PHPStan extension. Can automatically remove dead PHP code. Supports libraries like Symfony, Doctrine, PHPUnit etc. Detects dead cycles. Can detect dead code that is tested.

3462.2M52](/packages/shipmonk-dead-code-detector)[staabm/phpstan-dba

2912.3M2](/packages/staabm-phpstan-dba)

PHPackages © 2026

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