Custom Type
To implement a custom type, you should create a class that implements the TypeLang\Mapper\Type\TypeInterface interface.
This interface requires two methods to be implemented:
cast()- Converts external data into a value of a specific typematch()- Checks external data for type compliance
Let's see what your custom type will look like.
This is your "template" that will be present in all types.
Type Match
To add behavior there, you first need to define the task. Let's create int16 as an example. Its purpose will be to accept a PHP int and return a PHP int limited to 2 bytes (16 bits), for example, to implement a smallint in PostgreSQL. We will guarantee that the output value will not exceed this size.
First, you need to add a type check:
The match() method will be executed when calling the Mapper::isNormalizable() method, Mapper::isDenormalizable() an in union types, like: int16|string.
Type Cast
After implementing the match() method, the second cast() method should be implemented. This method should return the "casted" value. In our case, int16:
This is an extremely simple type that guarantees that for any input data, the output will be a value within int<-32768, 32767>.
Throwing an TypeLang\Mapper\Exception\Runtime\InvalidValueException exception in the cast() method is a simple and convenient way to report a problem with a value. You can use your own exceptions, such as the built-in \InvalidArgumentException, but this is not recommended.
So the complete code will look like this.
Context
You may have noticed that in addition to the value itself, a Context object is passed to each method. This Context object is completely immutable and contains runtime data: execution path; original value; configuration settings; more.
Context Value
You can retrieve the original value using the $context->value field from the Context object. This value may differ from the value passed in; for example, type coercion rules are not applied to it, so it can be used in exceptions to report information about the original data passed by the user.
Direction
Mapping direction is also runtime information. In most cases, different types will be used for different directions (if the behavior during normalization and denormalization differs), but sometimes you may need this information.
Information about the direction is located in the direction field of the Context object.
Configuration
As you might guess, the config property contains a Configuration object. However, it's important to note that the configuration object inside Mapper and the configuration object inside Context are different. For example, rules such as Configuration::$objectAsArray and Configuration::$strictTypes can be modified for a specific type. Thus, the configuration object will contain the new values specifically for that context.
For ease of use, methods such as isObjectAsArray() and isStrictTypesEnabled() can be called directly on the Context object.
Child Context
To process any composite types, such as PHP arrays or objects, a child context must be created. As mentioned above, the Context object itself is immutable, so to create a child context, a new object must be created using the enter() method, specifying information about the element being "entered".
For example, we have a task to create a type that should return an list<non-empty-string> from an input iterable<array-key, mixed> type.
An implementation will be look like this:
When creating a child context, you can also change the configuration rules by specifying it as the third argument.
Parent Context
In addition to the ability to create children, a context can also have a parent context that contains all the information about the Context of the parent type.
Please note that a parent is only contained by the context that has it, i.e., only the child. Therefore, be sure to perform the appropriate check.
Context Path
During execution, the Context collects the path the mapper traverses. This path can then be displayed in error messages or for other information.
To get the current path, call the getPath() method. This method will recursively traverse the entire history and return TypeLang\Mapper\Context\Path\PathInterface path object.
For example, we can print all the elements of the path in the type:
Interact With Types
The context contains a set of methods for obtaining types available in the current "mapping process".
The most necessary and popular ones will be the following:
$context->getTypeByValue($value)- Returns an object ofTypeInterfaceby passed PHP value. Where value is an absolutely arbitrary value whose type will be automatically inferred.$context->getTypeByDefinition($type)- Returns an object ofTypeInterfaceby passed type definition. Where definition is a PHP string with a description of the type, for example$type = "int<0, max>".
In addition, the context contains several helper methods that are worth knowing about, but you are unlikely to need them:
$context->getStatementByValue($value)- Returns an Abstract Syntax Tree (AST) of the inferred type of the value.$context->getStatementByDefinition($type)- Returns an AST by the type definition string.$context->getTypeByStatement($ast)- Returns an object ofTypeInterfaceby the type's AST.