Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 42 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,9 @@ use Superscript\Schema\Types\NumberType;
use Superscript\Schema\Sources\StaticSource;
use Superscript\Schema\Sources\ValueDefinition;
use Superscript\Schema\Resolvers\DelegatingResolver;
use Superscript\Schema\Resolvers\StaticResolver;
use Superscript\Schema\Resolvers\ValueResolver;

// Create a resolver with basic capabilities
$resolver = new DelegatingResolver([
StaticResolver::class,
ValueResolver::class,
]);
// Create a resolver
$resolver = new DelegatingResolver();

// Transform a string to a number
$source = new ValueDefinition(
Expand All @@ -62,15 +57,9 @@ use Superscript\Schema\Sources\StaticSource;
use Superscript\Schema\Sources\SymbolSource;
use Superscript\Schema\SymbolRegistry;
use Superscript\Schema\Resolvers\DelegatingResolver;
use Superscript\Schema\Resolvers\InfixResolver;
use Superscript\Schema\Resolvers\SymbolResolver;

// Set up resolver with symbol support
$resolver = new DelegatingResolver([
StaticResolver::class,
InfixResolver::class,
SymbolResolver::class,
]);

// Set up resolver
$resolver = new DelegatingResolver();

// Register symbols
$resolver->instance(SymbolRegistry::class, new SymbolRegistry([
Expand Down Expand Up @@ -235,15 +224,14 @@ new SymbolRegistry([
- Create clear separation between different symbol contexts
- Improve code maintainability with logical grouping

### Resolvers
### Resolver Architecture

Resolvers handle the evaluation of sources:
The library uses a unified resolver pattern where each source knows how to resolve itself:

- **StaticResolver**: Resolves static values
- **ValueResolver**: Applies type coercion using the `coerce()` method
- **InfixResolver**: Evaluates binary expressions
- **SymbolResolver**: Looks up named symbols
- **DelegatingResolver**: Chains multiple resolvers together
- **DelegatingResolver**: The main resolver that delegates to source-specific resolution logic
- Each source implements a `resolver()` method that returns a closure for resolving that source type
- Sources can accept dependencies (like SymbolRegistry) via the resolver's dependency injection container
- Custom resolution logic can be registered using `resolveUsing()` to override default behavior

### Operators

Expand Down Expand Up @@ -307,30 +295,48 @@ class EmailType implements Type
}
```

### Custom Resolvers
### Custom Source Resolution

Create specialized resolvers for specific data sources:
You can create custom resolution logic for specific source types:

```php
<?php

use Superscript\Schema\Resolvers\Resolver;
use Superscript\Schema\Resolvers\DelegatingResolver;
use Superscript\Schema\Source;
use Superscript\Monads\Result\Result;
use function Superscript\Monads\Result\Ok;
use function Superscript\Monads\Option\Some;

class DatabaseResolver implements Resolver
// Create a custom source
class DatabaseSource implements Source
{
public function resolve(Source $source): Result
{
// Custom resolution logic
// Connect to database, fetch data, etc.
}
public function __construct(
public string $query
) {}

public static function supports(Source $source): bool
public function resolver(): Closure
{
return $source instanceof DatabaseSource;
return function (DatabaseConnection $db) {
$result = $db->query($this->query);
return Ok(Some($result));
};
}
}

// Register the database connection
$resolver = new DelegatingResolver();
$resolver->instance(DatabaseConnection::class, $connection);

// Or override resolution for a specific source class
$resolver->resolveUsing(DatabaseSource::class, function (DatabaseSource $source, DatabaseConnection $db) {
$result = $db->query($source->query);
return Ok(Some($result));
});

// Resolve the source
$source = new DatabaseSource('SELECT * FROM users');
$result = $resolver->resolve($source);
```

## Development
Expand Down Expand Up @@ -366,8 +372,8 @@ composer test:infection # Mutation testing

The library follows several design patterns:

- **Strategy Pattern**: Different resolvers for different source types
- **Chain of Responsibility**: DelegatingResolver chains multiple resolvers
- **Strategy Pattern**: Each source type implements its own resolution strategy via the `resolver()` method
- **Dependency Injection**: DelegatingResolver uses a container to provide dependencies to source resolvers
- **Factory Pattern**: Type system for creating appropriate transformations
- **Functional Programming**: Extensive use of Result and Option monads

Expand Down
38 changes: 23 additions & 15 deletions src/Resolvers/DelegatingResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,26 @@

namespace Superscript\Schema\Resolvers;

use Closure;
use Illuminate\Container\Container;
use RuntimeException;
use Superscript\Schema\Source;
use Superscript\Monads\Option\Option;
use Superscript\Monads\Result\Result;
use Throwable;

final readonly class DelegatingResolver implements Resolver
final class DelegatingResolver implements Resolver
{
protected Container $container;
private Container $container;

/**
* @param array<class-string<Source>, class-string<Resolver>> $resolverMap
* @var array<string, Closure(never, never, never, never, never, never, never): Result<Option<mixed>, Throwable>>
*/
public function __construct(public array $resolverMap = [])
private array $resolveUsing = [];

public function __construct()
{
$this->container = new Container();
$this->container->instance(Resolver::class, $this);

foreach ($this->resolverMap as $resolver) {
$this->container->bind($resolver, $resolver);
}
}

/**
Expand All @@ -36,17 +34,27 @@ public function instance(string $key, mixed $concrete): void
$this->container->instance($key, $concrete);
}

public function resolveUsing(string $source, Closure $resolver): self
{
$this->resolveUsing[$source] = $resolver;

return $this;
}

/**
* @return Result<Option<mixed>, Throwable>
*/
public function resolve(Source $source): Result
{
$sourceClass = get_class($source);

if (isset($this->resolverMap[$sourceClass]) && $this->container->has($this->resolverMap[$sourceClass])) {
return $this->container->make($this->resolverMap[$sourceClass])->resolve($source);
}
/** @var Result<Option<mixed>, Throwable> */
return $this->container->call($this->getResolver($source));
}

throw new RuntimeException("No resolver found for source of type " . $sourceClass);
/**
* @return Closure(never, never, never, never, never, never, never): Result<Option<mixed>, Throwable>
*/
private function getResolver(Source $source): Closure
{
return $this->resolveUsing[$source::class] ?? $source->resolver();
}
}
42 changes: 0 additions & 42 deletions src/Resolvers/InfixResolver.php

This file was deleted.

4 changes: 0 additions & 4 deletions src/Resolvers/Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@
use Superscript\Monads\Result\Result;
use Throwable;

/**
* @template T of Source = Source
*/
interface Resolver
{
/**
* @phpstan-param T $source
* @return Result<Option<mixed>, Throwable>
*/
public function resolve(Source $source): Result;
Expand Down
24 changes: 0 additions & 24 deletions src/Resolvers/StaticResolver.php

This file was deleted.

27 changes: 0 additions & 27 deletions src/Resolvers/SymbolResolver.php

This file was deleted.

35 changes: 0 additions & 35 deletions src/Resolvers/UnaryResolver.php

This file was deleted.

33 changes: 0 additions & 33 deletions src/Resolvers/ValueResolver.php

This file was deleted.

13 changes: 12 additions & 1 deletion src/Source.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,15 @@

namespace Superscript\Schema;

interface Source {}
use Closure;
use Superscript\Monads\Option\Option;
use Superscript\Monads\Result\Result;

/**
* @template T = mixed
*/
interface Source
{
/** @return Closure(never, never, never, never, never, never, never): Result<Option<T>, \Throwable> */
public function resolver(): Closure;
}
Loading