Skip to content
Merged
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
1 change: 1 addition & 0 deletions documentation/components/libs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ It's designed to work together with static analysis tools like PHPStan and Psalm
The main goal of this library is to simplify common type-related tasks, such as type checking, type casting, and type assertion.

- [⬅️️ Back](../../introduction.md)
- [📖Architecture](/documentation/components/libs/types/architecture.md)
- [📚API Reference](/documentation/api/lib/types)
- [⚙️List of all Types](/api/lib/types/namespaces/flow-types-dsl.html)

Expand Down
156 changes: 156 additions & 0 deletions documentation/components/libs/types/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Types - Architecture

Types is a small library designed to ensure type consistency at runtime.
The concept is that wherever the system receives input data, one of three operations can be performed:

- `Type::isValid($value) : bool` - checks if the value is of the correct type
- `Type::cast($value) : mixed` - casts the value to a specific type, it must throw `\Flow\Types\Exception\CastingException` if the value
cannot be casted to the expected type
- `Type::assert($value) : void` - checks if the value is of the correct type, throwing an `Flow\Types\Exception\InvalidTypeException` exception if not

Each operation is executed at runtime, not compile time, but their structure provides significant type information for
static code analysis tools.

By using these functions, static analysis tools can predict undesirable situations in the code, such as passing an
incorrect type to a function or method.

```php
<?php

$userInput = $input->get('value'); // we expect this value to be an integer

if (\Flow\Types\DSL\type_integer()->isValid($userInput)) {
// at this point static analysis knows that $userInput is an integer
}

// make that value an int
$int = Flow\Types\DSL\type_integer()->cast($userInput);

// check if value is an integer, throws an exception if not
$int = Flow\Types\DSL\type_integer()->assert($userInput);
```

## Building Blocks

The main building block of this library is the interface [`Flow\Types\Type`](/src/lib/types/src/Flow/Types/Type.php).

This interface is implemented by two kinds of types:

- Native Types - those built into PHP, e.g., `int`, `string`, `bool`, `float`, `array`, `object`.
- Logical Types - those that narrow and refine native types, e.g., `type_uuid()` is a refinement of `type_string()` and
checks if a string is a valid UUID.

Additionally, all types are divided into:

- Composite Types - those composed of other types, e.g., `type_optional(type_string())` is equivalent to `?string`,
i.e., a string or null.
- Simple Types - single types, e.g., `type_string()`, `type_int()`, `type_bool()`, `type_float()`, `type_array()`,
`type_object()`.

## Architecture

The entire library is written following best object-oriented programming practices and design patterns. Each type is a
class implementing the `Flow\Types\Type` interface, enabling easy extension and addition of new types in the future.

To improve the developer experience, the library includes DSL (Domain Specific Language) functions that simplify
creating and using types in code.

For example, `new \Flow\Types\Type\Native\StringType()` is equivalent to `\Flow\Types\DSL\type_string()`.

This library also provides a set of helpers for working with types, that are also covered by the DSL.

For example, to determine a variable's type, you can use the `\Flow\Types\DSL\get_type($value) : Type` function, which
internally creates a new instance of `\Flow\Types\Type\TypeDetector` and calls its `detectType(mixed $value) : Type`
method.

The DSL definition can thus be considered the library's API, located in the
file [functions.php](src/lib/types/src/Flow/Types/DSL/functions.php).

One of the library's key principles is tight integration with static code analysis tools. This is achieved through
proper use of template mechanisms and type narrowing, `@template` and `@phpstan-assert`.

More details can be found at:

- [PHPStan Generic](https://phpstan.org/blog/generics-in-php-using-phpdocs)
- [Generic by Examples](https://phpstan.org/blog/generics-by-examples)
- [Narrowing Types](https://phpstan.org/writing-php-code/narrowing-types)

Therefore, it is critical that all classes and functions in this library are properly documented in PHPDoc.

## Testing

This library provides a set of unit tests that verify the correct operation of all types and DSL functions.
All tests are located in the directory [`Flow/Types/Tests/Unit`](src/lib/types/tests/Flow/Types/Tests/Unit).

Below is an example template that can be used to create cover a type with unit tests:

```php
<?php

declare(strict_types=1);

namespace Flow\Types\Tests\Unit\Type\Native;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Flow\Types\Exception\InvalidTypeException;

final class CustomTypeTest extends TestCase
{
public static function assert_data_provider() : \Generator
{
}

#[DataProvider('assert_data_provider')]
public function test_assert(mixed $value, ?string $exceptionClass = null) : void
{
// since assert is based on isValid() we should the same cases as for isValid() but we shold expect it to throw an exception.
}

public static function cast_data_provider() : \Generator
{
}

#[DataProvider('cast_data_provider')]
public function test_cast(mixed $value, mixed $expected, ?string $exceptionClass) : void
{
// we want to check if for given output method cast() returns expected value or throws an exception when given value can't be casted
}

public static function is_valid_data_provider() : \Generator
{
}

#[DataProvider('is_valid_data_provider')]
public function test_is_valid(mixed $value, bool $expected) : void
{
// we want to check if for given output method isValid() returns true or false
}

public funciton test_to_string() : void
{
// we want to check the output of toString() method for type
}

public function test_normalization() : void
{
// we want to use normalize() to turn type into an array representation
// then we want to use \Flow\Types\DSL\type_from_array() to create a new type from that array and compare it with the original type
}
}
```


Additionally, if a given type provides any extra functions, they should be tested in separate unit tests, covering all
possible use cases.

The Types library is part of the Flow PHP framework for data processing.
It is developed in a monorepository alongside other framework libraries and components.
All tools, such as Composer, PHPUnit, PHPStan, Rector, and CS Fixer, are available in the same location.

Tests can be executed using the following command at the monorepo root level:

```php
composer test:lib:types
composer static:analyze
```
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Flow\ETL\Exception\InvalidArgumentException;
use Flow\ETL\Schema;
use Flow\ETL\Schema\{Definition, Metadata};
use Flow\Types\Type;
use Flow\Types\Type\Logical\{DateTimeType,
DateType,
JsonType,
Expand All @@ -22,7 +23,6 @@
XMLElementType,
XMLType};
use Flow\Types\Type\Native\{BooleanType, FloatType, IntegerType, StringType};
use Flow\Types\Type\Type;

final readonly class SchemaConverter
{
Expand All @@ -46,7 +46,7 @@
private TypesMap $typesMap;

/**
* @param array<class-string<Type<mixed>>, class-string<\Doctrine\DBAL\Types\Type>> $map
* @param array<class-string<\Flow\Types\Type<mixed>>, class-string<\Doctrine\DBAL\Types\Type>> $map
*/
public function __construct(array $map = [])
{
Expand Down Expand Up @@ -145,7 +145,7 @@ private function columnToFlow(Column $column, Table $table) : Definition
}

/**
* @param Type<mixed> $type
* @param \Flow\Types\Type<mixed> $type
*/
private function flowToColumn(string $name, Type $type, bool $nullable, ?Metadata $metadata = null) : Column
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\DBAL\Types\{BigIntType, BlobType, DateImmutableType, DateTimeImmutableType, DateTimeTzImmutableType, DateTimeTzType, DecimalType, GuidType, SmallFloatType, SmallIntType, TextType, TimeImmutableType};
use Doctrine\DBAL\Types\{Type as DbalType};
use Flow\ETL\Exception\InvalidArgumentException;
use Flow\Types\Type as FlowType;
use Flow\Types\Type\Logical\{DateTimeType,
DateType,
JsonType,
Expand All @@ -18,7 +19,6 @@
XMLElementType,
XMLType};
use Flow\Types\Type\Native\{BooleanType, FloatType, IntegerType, StringType};
use Flow\Types\Type\Type as FlowType;

final class TypesMap
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Flow\ETL\{Schema};
use Flow\Parquet\ParquetFile\Schema as ParquetSchema;
use Flow\Parquet\ParquetFile\Schema\{Column, FlatColumn, ListElement, NestedColumn};
use Flow\Types\Type;
use Flow\Types\Type\Logical\{DateTimeType,
DateType,
JsonType,
Expand All @@ -34,7 +35,6 @@
XMLElementType,
XMLType};
use Flow\Types\Type\Native\{BooleanType, FloatType, IntegerType, StringType};
use Flow\Types\Type\Type;

final class SchemaConverter
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
StructureEntry,
UuidEntry,
XMLEntry};
use Flow\Types\Type;
use Flow\Types\Type\Logical\{ListType, MapType, StructureType};
use Flow\Types\Type\Type;

final readonly class EntryNormalizer
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Flow\ETL\Exception\InvalidArgumentException;
use Flow\Types\Type\Logical\{DateTimeType, InstanceOfType, JsonType, ListType, MapType, StructureType, UuidType};
use Flow\Types\Type\Native\{ArrayType, BooleanType, EnumType, FloatType, IntegerType, StringType};
use Flow\Types\Type\{Type};
use Flow\Types\{Type};

final readonly class PHPValueNormalizer
{
Expand Down
13 changes: 7 additions & 6 deletions src/core/etl/src/Flow/ETL/DSL/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
use Flow\Filesystem\{Filesystem, Local\NativeLocalFilesystem, Partition, Partitions, Path};
use Flow\Filesystem\Stream\Mode;
use Flow\Serializer\{NativePHPSerializer, Serializer};
use Flow\Types\Type;
use Flow\Types\Type\Logical\{DateTimeType,
DateType,
InstanceOfType,
Expand All @@ -189,7 +190,7 @@
StringType,
UnionType
};
use Flow\Types\Type\{Type, Types};
use Flow\Types\Type\{Types};
use UnitEnum;

/**
Expand Down Expand Up @@ -588,7 +589,7 @@ function type_structure(array $elements) : StructureType
* @template T
*
* @param Type<T> $first
* @param Type<T> $second
* @param \Flow\Types\Type<T> $second
* @param Type<T> ...$types
*
* @return UnionType<T, T>
Expand All @@ -604,7 +605,7 @@ function type_union(Type $first, Type $second, Type ...$types) : UnionType
/**
* @template T
*
* @param Type<T> $type
* @param \Flow\Types\Type<T> $type
*
* @return OptionalType<T>
*
Expand All @@ -630,7 +631,7 @@ function type_from_array(array $data) : Type
}

/**
* @param Type<mixed> $type
* @param \Flow\Types\Type<mixed> $type
*
* @deprecated please use \Flow\Types\DSL\is_nullable(Type $type) : bool
*/
Expand Down Expand Up @@ -1120,7 +1121,7 @@ function hash(mixed $value, Algorithm $algorithm = new NativePHPHash()) : Hash
}

/**
* @param string|Type<mixed> $type
* @param \Flow\Types\Type<mixed>|string $type
*/
#[DocumentationDSL(module: Module::CORE, type: DSLType::SCALAR_FUNCTION)]
function cast(mixed $value, string|Type $type) : Cast
Expand Down Expand Up @@ -1888,7 +1889,7 @@ function is_type(Type|array $type, mixed $value) : bool
/**
* @template T
*
* @param Type<T> $type
* @param \Flow\Types\Type<T> $type
* @param class-string<Type<mixed>> $typeClass
*
* @deprecated please use \Flow\Types\DSL\type_is($type, $typeClass): bool instead
Expand Down
2 changes: 1 addition & 1 deletion src/core/etl/src/Flow/ETL/Function/Cast.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Flow\ETL\Function\ScalarFunction\ScalarResult;
use Flow\ETL\Row;
use Flow\Types\Exception\CastingException;
use Flow\Types\Type\{Type};
use Flow\Types\{Type};

final class Cast extends ScalarFunctionChain
{
Expand Down
3 changes: 2 additions & 1 deletion src/core/etl/src/Flow/ETL/Function/IsType.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
namespace Flow\ETL\Function;

use Flow\ETL\Row;
use Flow\Types\Type\{Type, TypeFactory};
use Flow\Types\Type;
use Flow\Types\Type\{TypeFactory};

final class IsType extends ScalarFunctionChain
{
Expand Down
2 changes: 1 addition & 1 deletion src/core/etl/src/Flow/ETL/Function/Parameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Flow\ETL\Function\ScalarFunction\ScalarResult;
use Flow\ETL\Row;
use Flow\ETL\Row\{Entry, Reference};
use Flow\Types\Type\Type;
use Flow\Types\Type;
use UnitEnum;

final readonly class Parameter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
namespace Flow\ETL\Function\ScalarFunction;

use function Flow\Types\DSL\type_optional;
use Flow\Types\Type\{Type, TypeDetector};
use Flow\Types\Type;
use Flow\Types\Type\{TypeDetector};

final readonly class ScalarResult
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use Flow\ETL\Function\StyleConverter\StringStyles as OldStringStyles;
use Flow\ETL\Hash\{Algorithm, NativePHPHash};
use Flow\ETL\String\StringStyles;
use Flow\Types\Type\Type;
use Flow\Types\Type;

abstract class ScalarFunctionChain implements ScalarFunction
{
Expand Down
2 changes: 1 addition & 1 deletion src/core/etl/src/Flow/ETL/Row/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Flow\ETL\Row;

use Flow\ETL\Schema\Definition;
use Flow\Types\Type\Type;
use Flow\Types\Type;

/**
* @template-covariant TValue of mixed
Expand Down
2 changes: 1 addition & 1 deletion src/core/etl/src/Flow/ETL/Row/Entry/BooleanEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
use Flow\ETL\Exception\InvalidArgumentException;
use Flow\ETL\Row\{Entry, Reference};
use Flow\ETL\Schema\{Definition, Metadata};
use Flow\Types\Type;
use Flow\Types\Type\Native\BooleanType;
use Flow\Types\Type\Type;

/**
* @implements Entry<?bool, bool>
Expand Down
2 changes: 1 addition & 1 deletion src/core/etl/src/Flow/ETL/Row/Entry/DateEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
use Flow\ETL\Exception\InvalidArgumentException;
use Flow\ETL\Row\{Entry, Reference};
use Flow\ETL\Schema\{Definition, Metadata};
use Flow\Types\Type;
use Flow\Types\Type\Logical\DateType;
use Flow\Types\Type\Type;

/**
* @implements Entry<?DateTimeInterface, DateTimeInterface>
Expand Down
2 changes: 1 addition & 1 deletion src/core/etl/src/Flow/ETL/Row/Entry/DateTimeEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
use Flow\ETL\Exception\InvalidArgumentException;
use Flow\ETL\Row\{Entry, Reference};
use Flow\ETL\Schema\{Definition, Metadata};
use Flow\Types\Type;
use Flow\Types\Type\Logical\DateTimeType;
use Flow\Types\Type\Type;

/**
* @implements Entry<?DateTimeInterface, DateTimeInterface>
Expand Down
Loading