Skip to content

Commit 5f0ee99

Browse files
committed
Initial commit
0 parents  commit 5f0ee99

19 files changed

+552
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
vendor
3+
composer.lock
4+
build

.travis.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
language: php
2+
sudo: false
3+
php:
4+
- 7.1
5+
- 7.2
6+
7+
before_install:
8+
- composer self-update
9+
10+
install:
11+
- travis_retry composer install --no-interaction --prefer-source
12+
13+
script:
14+
- vendor/bin/phpstan analyze src/ test/ --level max
15+
- vendor/bin/phpunit --coverage-clover build/logs/clover.xml
16+
17+
after_success:
18+
- travis_retry php vendor/bin/php-coveralls -v

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2018 Vsevolod Vietluzhskykh
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# TypeGuard - PHP type validation partly inspired by [Ceylon Union and Intersection types](https://ceylon-lang.org/documentation/1.3/tour/types/)
2+
3+
[![Build Status](https://travis-ci.com/Sevavietl/TypeGuard.svg?branch=master)](https://travis-ci.com/Sevavietl/TypeGuard)
4+
[![Coverage Status](https://coveralls.io/repos/github/Sevavietl/TypeGuard/badge.svg)](https://coveralls.io/github/Sevavietl/TypeGuard)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6+
[![PHPStan](https://img.shields.io/badge/PHPStan-enabled-brightgreen.svg?style=flat)](https://github.com/phpstan/phpstan)
7+
8+
## Features
9+
10+
TypeGuard can validate:
11+
12+
- Scalar types: `string`, `integer`, etc.:
13+
14+
```php
15+
(new \TypeGuard\Type('string'))->match('foo'); // => true
16+
```
17+
18+
- Object types: `ArrayAccess`, `stdClass`, etc.:
19+
20+
```php
21+
(new \TypeGuard\Type('stdClass'))->match(new stdClass()); // => true
22+
```
23+
24+
- Union types: `string|integer`:
25+
26+
```php
27+
$guard = new \TypeGuard\Type('string|integer');
28+
$guard->match('foo'); // => true
29+
$guard->match(1); // => true
30+
```
31+
32+
- Intersection types: `ArrayAccess&Countable`:
33+
34+
```php
35+
(new \TypeGuard\Type('ArrayAccess&Countable'))->match(new ArrayIterator()); // => true
36+
```
37+
38+
- Optional types: `?string`:
39+
40+
```php
41+
(new \TypeGuard\Type('?string'))->match(null); // => true
42+
```
43+

composer.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "sevavietl/type-guard",
3+
"description": "PHP type validator",
4+
"type": "library",
5+
"require": {
6+
"php": ">=7.1",
7+
"php-coveralls/php-coveralls": "^2.1"
8+
},
9+
"require-dev": {
10+
"phpunit/phpunit": "^7.3",
11+
"phpstan/phpstan": "^0.10.3"
12+
},
13+
"autoload": {
14+
"psr-4": {
15+
"TypeGuard\\": "src/"
16+
}
17+
},
18+
"autoload-dev": {
19+
"psr-4": {
20+
"TypeGuard\\Test\\": "test/"
21+
}
22+
},
23+
"license": "MIT",
24+
"authors": [
25+
{
26+
"name": "sevavietl",
27+
"email": "[email protected]"
28+
}
29+
]
30+
}

phpunit.xml.dist

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit backupGlobals="false"
3+
backupStaticAttributes="false"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
convertErrorsToExceptions="true"
7+
convertNoticesToExceptions="true"
8+
convertWarningsToExceptions="true"
9+
processIsolation="false"
10+
stopOnFailure="false">
11+
<filter>
12+
<whitelist processUncoveredFilesFromWhitelist="true">
13+
<directory suffix=".php">./src</directory>
14+
</whitelist>
15+
</filter>
16+
<testsuites>
17+
<testsuite name="unit">
18+
<directory suffix="Test.php">test/</directory>
19+
</testsuite>
20+
</testsuites>
21+
</phpunit>

src/ArrayType.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace TypeGuard;
4+
5+
final class ArrayType implements TypeInterface
6+
{
7+
/** @var TypeInterface */
8+
private $type;
9+
10+
public function __construct(TypeInterface $type)
11+
{
12+
$this->type = $type;
13+
}
14+
15+
/**
16+
* @param mixed $parameter
17+
* @return bool
18+
*/
19+
public function match($parameter): bool
20+
{
21+
$type = \gettype($parameter);
22+
23+
if ($type !== 'array') {
24+
return false;
25+
}
26+
27+
foreach ($parameter as $item) {
28+
if (! $this->type->match($item)) {
29+
return false;
30+
}
31+
}
32+
33+
return true;
34+
}
35+
}

src/Guard.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace TypeGuard;
4+
5+
final class Guard
6+
{
7+
/** @var TypeInterface */
8+
private $type;
9+
10+
public function __construct(string $typeName)
11+
{
12+
if ($optional = $this->isOptional($typeName)) {
13+
$typeName = substr($typeName, 1);
14+
}
15+
16+
if ($array = $this->isArray($typeName)) {
17+
$typeName = substr($typeName, 0, -2);
18+
}
19+
20+
if ($this->isUnion($typeName)) {
21+
$type = new UnionType(array_filter(explode(UnionType::SEPARATOR, $typeName), 'trim'));
22+
} elseif ($this->isIntersection($typeName)) {
23+
$type = new IntersectionType(array_filter(explode(IntersectionType::SEPARATOR, $typeName), 'trim'));
24+
} else {
25+
$type = new Type($typeName);
26+
}
27+
28+
$type = $array ? new ArrayType($type) : $type;
29+
30+
$this->type = $optional ? new OptionalType($type) : $type;
31+
}
32+
33+
private function isOptional(string $typeName): bool
34+
{
35+
return strpos($typeName, '?') === 0;
36+
}
37+
38+
private function isArray(string $typeName): bool
39+
{
40+
return (bool) preg_match('/(?:\[\])$/', $typeName);
41+
}
42+
43+
private function isUnion(string $typeName): bool
44+
{
45+
return strpos($typeName, UnionType::SEPARATOR) !== false;
46+
}
47+
48+
private function isIntersection(string $typeName): bool
49+
{
50+
return strpos($typeName, IntersectionType::SEPARATOR) !== false;
51+
}
52+
53+
public function match($parameter): bool
54+
{
55+
return $this->type->match($parameter);
56+
}
57+
}

src/IntersectionType.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace TypeGuard;
4+
5+
final class IntersectionType implements TypeInterface
6+
{
7+
public const SEPARATOR = '&';
8+
9+
/** @var TypeInterface[] */
10+
private $types;
11+
12+
public function __construct(array $typeNames)
13+
{
14+
$this->types = array_map(function ($typeName): TypeInterface {
15+
return new Type($typeName);
16+
}, $typeNames);
17+
}
18+
19+
/**
20+
* @param mixed $parameter
21+
* @return bool
22+
*/
23+
public function match($parameter): bool
24+
{
25+
return array_reduce($this->types, function (bool $carry, TypeInterface $type) use ($parameter): bool {
26+
return $carry && $type->match($parameter);
27+
}, true);
28+
}
29+
}

src/OptionalType.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace TypeGuard;
4+
5+
final class OptionalType implements TypeInterface
6+
{
7+
/** @var TypeInterface */
8+
private $type;
9+
10+
public function __construct(TypeInterface $type)
11+
{
12+
$this->type = $type;
13+
}
14+
15+
/**
16+
* @param mixed $parameter
17+
* @return bool
18+
*/
19+
public function match($parameter): bool
20+
{
21+
$type = \gettype($parameter);
22+
23+
if ($type === 'NULL') {
24+
return true;
25+
}
26+
27+
return $this->type->match($parameter);
28+
}
29+
}

src/Type.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace TypeGuard;
4+
5+
final class Type implements TypeInterface
6+
{
7+
/** @var string */
8+
private $typeName;
9+
10+
public function __construct(string $typeName)
11+
{
12+
$this->typeName = $typeName;
13+
}
14+
15+
/**
16+
* @param mixed $parameter
17+
* @return bool
18+
*/
19+
public function match($parameter): bool
20+
{
21+
$type = \gettype($parameter);
22+
23+
return $type === 'object'
24+
? $parameter instanceof $this->typeName
25+
: $type === $this->typeName;
26+
}
27+
}

src/TypeInterface.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace TypeGuard;
4+
5+
interface TypeInterface
6+
{
7+
/**
8+
* @param mixed $parameter
9+
* @return bool
10+
*/
11+
public function match($parameter): bool;
12+
}

src/UnionType.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace TypeGuard;
4+
5+
final class UnionType implements TypeInterface
6+
{
7+
public const SEPARATOR = '|';
8+
9+
/** @var TypeInterface[] */
10+
private $types;
11+
12+
public function __construct(array $typeNames)
13+
{
14+
$this->types = array_map(function ($typeName) {
15+
return new Type($typeName);
16+
}, $typeNames);
17+
}
18+
19+
/**
20+
* @param mixed $parameters
21+
* @return bool
22+
*/
23+
public function match($parameters): bool
24+
{
25+
return array_reduce($this->types, function (bool $carry, TypeInterface $type) use ($parameters): bool {
26+
return $carry || $type->match($parameters);
27+
}, false);
28+
}
29+
}

0 commit comments

Comments
 (0)