Skip to content

[WIP] TypeInfo refactoring / finetuning#1

Open
skalpa wants to merge 1 commit into
masterfrom
type-info/type-accepts
Open

[WIP] TypeInfo refactoring / finetuning#1
skalpa wants to merge 1 commit into
masterfrom
type-info/type-accepts

Conversation

@skalpa

@skalpa skalpa commented Aug 16, 2024

Copy link
Copy Markdown
Owner

Introduction

My initial objective was to get full types "comparison", so we can determine that resolve('iterable')->accepts(resolve('array<string, MyClass>')).

I use the component in my own serializer (just saw the Vienna schedule... it's called ObjectFactory and Normalizer here and not ObjectMapper but it's something we may talk about later), and in code generators (I generate Typescript classes from my PHP domain).

Therefore I appreciate the fact that the Type classes are (near) independent of the PHP runtime and allows Type::object('NonExistentClass') (but it needs to be improved: some classes depend on is_a()).

Uses cases to keep in mind:

  • Create a type system that knows that number = int|float
  • Create an object type named DateTime that declares that it implements an interface named DateInterface but NOT DateTimeInterface

Type identifiers / primitives

Each type must identity itself using a type identifier. They represent the primitive types supported by a type system (null, int, string, array, object...) and are used as the foundation of the types comparison system (so a bool will not accept a string, but an iterable will accept an array).

I originally went for TypeIdentifier|string but settled for the current implementation as it keeps the Type classes decoupled from the core TypeIdentifier::accepts() logic and makes it reusable (see below how we can now create custom atomic types that behave like iterable or whatever).

Note: We cannot completely decouple all our Type subclasses from TypeIdentifier as some of them need to know what the canon object type is in the system. That makes them kind of PHP-only as TypeIdentifier is really PHP primitives. That works but I've been thinking of extracting a TypeIdentifier enum with "universal" primitives one is likely to find in any language and add a PhpTypeIdentifier with the PHP primitives (so we can make ObjectType and CollectionType depend on TypeIdentifier but not on PhpTypeIdentifier). Now that would come with more BC breaks and might feel too anal so I'm not sure.

Atomic types

Atomic types are basically defined by a name followed by optional variable types. I.e. MyType<int>.

Support for generics/variable types is implemented there and can be reused by subclasses. It is assumed that the variable types restrict a base type (so MyType<int> is a subset of MyType).

The class can be extended or used directly (actually I started to refactor our own classes in dogfood mode so they now extend AtomicType).

The constructor accepts a TypeIdentifier, making the type behavior independent of its name (so we can create a Type::atomic(TypeIdentifier::INT, 'positive-int') that behaves like an INT or create a never<int> type that behaves like a FLOAT with Type::atomic(TypeIdentifier::FLOAT, 'never', Type::int()).

Nullability can also be configured. If you set isNullable to true, AtomicType will behave exactly like null (isNullable() will return true, and asNonNullable() will throw an exception). More complex nullability behavior can be implemented by subclasses (that's how BuiltinType handles MIXED).

$compareName is here to allow configuring whether types that identify using the same primitive should be considered compatible (so the user can create an odd-number and an even-number that are both INT but don't accept each others).

Note: that came up after talking to stof. We want the component to be able to make sense out of some types (i.e. know what the non nullable version of mixed is), but also allow it to describe types that do not make any sense in PHP like never<void>. This class allows us to make a difference between never, which would be the corresponding BuiltinType, and never<void> which is a custom atomic type named "never".

Object type [WIP]

Wrapping concept was removed. Object types are now atomic types (generics supports is handled by the base class) that identify themselves as TypeIdentifier::OBJECT.

If we want to decouple this class from the PHP runtime, we need to be able to specify which contracts the type supports when creating it. I.e. Type::object(['DateTime', 'DateInterface']) should be accepted wherever a DateTime or a DateInterface is accepted, but NOT where a DateTimeInterface is accepted, even though there's a PHP class that behaves this way. (We can update the resolvers and make them create types will all the necessary parents/interfaces during resolution).

Collections

Composite types

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant