Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typed Arrays #21

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
/vendor/
*.swp
*.swo
/.idea
10 changes: 10 additions & 0 deletions src/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ public function as(string $type): mixed
return $this->variable;
}

/**
* Create a new TypedArray instance.
*
* @return TypedArray<TVariable>
*/
public function asArrayOf(): TypedArray
{
return new TypedArray($this->variable);
}

/**
* Asserts and narrow down the type to string.
*
Expand Down
95 changes: 95 additions & 0 deletions src/TypedArray.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace GlossPHP\TypeGuard;

use TypeError;

/**
* @internal
*
* @template TVariable
*/
final readonly class TypedArray
{
/**
* Create a new TypedArray instance.
*
* @param TVariable $variable
*/
public function __construct(private mixed $variable)
{
}

/**
* Asserts and narrow down the type to array<int>.
*
* @phpstan-assert-if-true int[] $this->variable
*
* @phpstan-return (TVariable is int[] ? int[] : never)
*/
public function int(): array
{
/** @phpstan-assert non-empty-array<int> $this->variable */
if (! is_array($this->variable) || $this->variable === []) {
throw new TypeError('Array must be a non-empty array of specified int.');
}

foreach ($this->variable as $item) {
if (! is_int($item)) {
throw new TypeError('Array value {'.$item.'} is not [int].');
}
}

return $this->variable;
}

/**
* Asserts and narrow down the type to array<string>.
*
* @phpstan-assert-if-true string[] $this->variable
*
* @phpstan-return (TVariable is string[] ? string[] : never)
*/
public function string(): array
{
/** @phpstan-assert non-empty-array<string> $this->variable */
if (! is_array($this->variable) || $this->variable === []) {
throw new TypeError('Array must be a non-empty array of specified string.');
}

foreach ($this->variable as $item) {
if (! is_string($item)) {
throw new TypeError('Array value {'.$item.'} is not [string].');
}
}

return $this->variable;
}

/**
* Asserts and narrow down the type to the given type.
*
* @template TAs
*
* @param class-string<TAs> $type
*
* @phpstan-return TAs[]
*/
public function class(string $type): array
{
/** @phpstan-assert non-empty-array<TAs> $this->variable */
if (! is_array($this->variable) || $this->variable === []) {
throw new TypeError('Array must be a non-empty array of specified '.$type.'.');
}

foreach ($this->variable as $item) {
if (! is_object($item) || ! $item instanceof $type) {
throw new TypeError('Array value {'.$item.'} is not ['.$type.'].');
}
}

return $this->variable;
}
}
38 changes: 38 additions & 0 deletions tests/Array/AsArrayClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

final class Test
{
}

test('array typed Test', function (): void {
$variable = new Test();
$array = [$variable];

$userArray = type($array)
->asArrayOf()
->class(Test::class);

expect($userArray)->toBeArray();
foreach ($userArray as $value) {
expect($value)->toBeInstanceOf(Test::class);
}
});

test('not Test array', function (): void {
$variable = new Test();
$variable = [$variable, 7415541];

type($variable)
->asArrayOf()
->class(Test::class);
})->throws(TypeError::class, 'Array value {7415541} is not [Test].');

test('empty array', function (): void {
$variable = [];

type($variable)
->asArrayOf()
->class(Test::class);
})->throws(TypeError::class, 'Array must be a non-empty array of specified Test.');
26 changes: 26 additions & 0 deletions tests/Array/AsArrayInt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

test('array typed int', function (): void {
$array = [3, 42, 42];

$intArray = type($array)->asArrayOf()->int();

expect($intArray)->toBeArray();
foreach ($intArray as $value) {
expect($value)->toBeInt();
}
});

test('not int array', function (): void {
$variable = [7415541, 'error', 4134];

type($variable)->asArrayOf()->int();
})->throws(TypeError::class, 'Array value {error} is not [int].');

test('empty array', function (): void {
$variable = [];

type($variable)->asArrayOf()->int();
})->throws(TypeError::class, 'Array must be a non-empty array of specified int.');
32 changes: 32 additions & 0 deletions tests/Array/asArrayString.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

test('array typed string', function (): void {
$array = ['test', '42', 'ofads'];

$stringArray = type($array)
->asArrayOf()
->string();

expect($stringArray)->toBeArray();
foreach ($stringArray as $value) {
expect($value)->toBeString();
}
});

test('not string array', function (): void {
$variable = ['test', 7415541, 4134];

type($variable)
->asArrayOf()
->string();
})->throws(TypeError::class, 'Array value {7415541} is not [string].');

test('empty array', function (): void {
$variable = [];

type($variable)
->asArrayOf()
->string();
})->throws(TypeError::class, 'Array must be a non-empty array of specified string.');