Skip to content

HypothesisPHP/Lens

Repository files navigation

Lens

Lens is a reusable object that knows how to locate, read, and transform a specific piece of data inside any object or array.


Installation

composer require hypothesisphp/lens

Quick Start

use Lens;

// Get a nested value
$city = Lens::path('address.city')->get($user);

// Set (immutable — returns a new copy)
$updated = Lens::path('address.city')->set($user, 'Bandung');

// Update (transform in place)
$updated = Lens::path('price')->update($product, fn($v) => $v * 1.1);

// Batch (single clone, multiple changes)
$updated = Lens::batch($user)
    ->set('profile.name', 'John')
    ->update('profile.age', fn($v) => $v + 1)
    ->set('address.city', 'Bandung')
    ->apply();

Why Lens?

The Problem

Working with nested data in PHP is verbose and error-prone:

// Without Lens
$clone = clone $user;
$clone->address = clone $user->address;
$clone->address->city = 'Bandung';
$clone->profile = clone $user->profile;
$clone->profile->name = 'John';
$clone->profile->age = $user->profile->age + 1;

The Solution

// With Lens
$clone = Lens::batch($user)
    ->set('address.city', 'Bandung')
    ->set('profile.name', 'John')
    ->update('profile.age', fn($v) => $v + 1)
    ->apply(); // ONE clone, all changes applied

Features

Read

// Simple property
Lens::path('name')->get($user);          // 'Alice'

// Deeply nested
Lens::path('orders.0.items.2.price')->get($user); // 25.0

// Wildcard (all elements)
Lens::path('orders.*.price')->get($user); // [100, 200, 300]

// Nested wildcards
Lens::path('orders.*.items.*.price')->get($user); // [[10, 20], [30]]

Write (Immutable)

// Returns a NEW object — original unchanged
$updated = Lens::path('name')->set($user, 'Bob');
// $user->name is still 'Alice'
// $updated->name is 'Bob'

// Deep set
$updated = Lens::path('address.city')->set($user, 'Bandung');

Transform

// Update with a callable
$updated = Lens::path('age')->update($user, fn($v) => $v + 1);

// Update deeply nested
$updated = Lens::path('address.zip')->update($user, fn($v) => strtoupper($v));

Mutable Mode

// Modifies the original object in place
Lens::path('name')->mutate($user, 'Bob');
// $user->name is now 'Bob'

Batch Operations

// Multiple changes, single clone
$updated = Lens::batch($user)
    ->set('name', 'Bob')
    ->set('age', 25)
    ->update('address.zip', fn($v) => $v . '-001')
    ->apply();

// Mutable batch (objects only)
Lens::batch($user)
    ->set('name', 'Bob')
    ->applyAs('mutable');

Nullable & Default

// Returns null instead of throwing
Lens::path('address.city')->nullable()->get($userWithoutAddress); // null

// Returns default value
Lens::path('address.city')->default('Unknown')->get($userWithoutAddress); // 'Unknown'

Composition

$addressLens = Lens::path('address');
$cityLens = Lens::path('city');
$addressCityLens = $addressLens->compose($cityLens);

$city = $addressCityLens->get($user);

Pipelines

Lens::pipeline()
    ->pipe(fn($v) => strtoupper($v))
    ->pipe(fn($v) => trim($v))
    ->through(Lens::path('name'))
    ->get($user); // 'ALICE'

Path Syntax

Syntax Example Meaning
Property name Access name property/key
Dotted address.city Deep access
Index orders.0 Array index
Wildcard orders.* All elements
Quoted "special.key" Key with dots
Escaped foo\.bar Literal dot in key

Exists & Unset

Lens::path('name')->exists($user); // true/false

// Remove a key (immutable)
$updated = Lens::path('name')->unset($data);

Works With

  • Objects — public, protected, private properties
  • Arrays — associative and indexed
  • Mixed — objects containing arrays containing objects
  • ArrayAccessArrayObject, custom implementations
  • DTOs — readonly properties
  • Value Objects — immutable objects

Documentation

Topic Description
Getting Started Install and first steps
Path Syntax All path expressions
Immutable vs Mutable When to use each mode
Batch Operations Single-clone batch changes
Composition Combining lenses
Traversals Focusing on multiple targets
Pipelines Chaining transformations
Attributes PHP 8 Attribute configuration
Extending Custom strategies, plugins, macros
Performance Optimization details
Static Analysis PHPStan & Psalm
Migration Guide Moving from other approaches
Architecture Complete design document

Extensibility

Custom Clone Strategy

use Lens\Contracts\CloneStrategyInterface;

class MyCloneStrategy implements CloneStrategyInterface
{
    public function clone(mixed $data): mixed { /* ... */ }
    public function supports(mixed $data): bool { /* ... */ }
}

Custom Metadata Provider

use Lens\Contracts\MetadataProviderInterface;

class DoctrineMetadataProvider implements MetadataProviderInterface
{
    // Read types from Doctrine metadata instead of reflection
}

Macros

Lens::macro('shout', function (string $value): string {
    return strtoupper($value) . '!';
});

Plugins & Middleware

use Lens\Contracts\PluginInterface;
use Lens\Contracts\MiddlewareInterface;

// Log every lens operation
class AuditPlugin implements PluginInterface { /* ... */ }

// Cache lens reads
class CacheMiddleware implements MiddlewareInterface { /* ... */ }

Static Analysis

Fully compatible with PHPStan (level max) and Psalm (level 1). Generic type annotations:

/** @var LensInterface<User, string> */
$lens = Lens::path('name');

Requirements

  • PHP 8.2+
  • ext-mbstring

Architecture

The library is modular and interface-driven. Every component is independently replaceable:

Contracts → Operations → Navigation → Access → Strategy → Infrastructure

See ARCHITECTURE.md for the complete design document.

Testing

composer test              # All tests
composer test:unit         # Unit tests only
composer test:integration  # Integration tests
composer test:feature      # Feature tests
composer analyse           # PHPStan
composer psalm             # Psalm
composer mutate            # Infection (mutation testing)

License

MIT License. See LICENSE.

Credits

Inspired by functional programming lenses (Haskell, Scala, Monocle) — redesigned for PHP.

About

Lens is a reusable object that knows how to locate, read, and transform a specific piece of data inside any object or array.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages