Skip to content
This repository has been archived by the owner on Jul 28, 2023. It is now read-only.
/ type Public archive
generated from spaceonfire/skeleton

Commit

Permalink
feat(type): add types
Browse files Browse the repository at this point in the history
  • Loading branch information
hustlahusky committed Jun 1, 2020
1 parent e83aea8 commit e467f15
Show file tree
Hide file tree
Showing 14 changed files with 1,026 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
### Security
- Nothing
-->

## [1.0.0] - 2020-06-01
- First release
208 changes: 208 additions & 0 deletions src/BuiltinType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<?php

declare(strict_types=1);

namespace spaceonfire\Type;

use InvalidArgumentException;
use Webmozart\Assert\Assert;

final class BuiltinType implements Type
{
public const INT = 'int';
public const FLOAT = 'float';
public const STRING = 'string';
public const BOOL = 'bool';
public const RESOURCE = 'resource';
public const OBJECT = 'object';
public const ARRAY = 'array';
public const NULL = 'null';
public const CALLABLE = 'callable';
public const ITERABLE = 'iterable';

private const SUPPORT_NO_STRICT = [
self::INT => true,
self::FLOAT => true,
self::STRING => true,
];

/**
* @var string
*/
private $type;
/**
* @var bool
*/
private $strict;

/**
* BuiltinType constructor.
* @param string $type
* @param bool $strict
*/
public function __construct(string $type, bool $strict = true)
{
if (!self::supports($type)) {
throw new InvalidArgumentException(sprintf('Type "%s" is not supported by %s', $type, __CLASS__));
}

$this->type = self::prepareType($type);

if ($strict === false && !isset(self::SUPPORT_NO_STRICT[$this->type])) {
$strict = true;
trigger_error(sprintf('Type "%s" cannot be non-strict. $strict argument overridden.', $this->type));
}

$this->strict = $strict;
}

/**
* @inheritDoc
*/
public function check($value): bool
{
try {
switch ($this->type) {
case self::INT:
if ($this->strict) {
Assert::integer($value);
} else {
Assert::integerish($value);
}
break;

case self::FLOAT:
if ($this->strict) {
Assert::float($value);
} else {
Assert::numeric($value);
}
break;

case self::STRING:
if ($this->strict) {
Assert::string($value);
} elseif (is_object($value)) {
Assert::methodExists($value, '__toString');
} else {
Assert::scalar($value);
}
break;

case self::BOOL:
Assert::boolean($value);
break;

case self::RESOURCE:
Assert::resource($value);
break;

case self::OBJECT:
Assert::object($value);
break;

case self::ARRAY:
Assert::isArray($value);
break;

case self::NULL:
Assert::null($value);
break;

case self::CALLABLE:
Assert::isCallable($value);
break;

case self::ITERABLE:
Assert::isIterable($value);
break;
}

return true;
} catch (InvalidArgumentException $exception) {
return false;
}
}

/**
* Cast value to current type
* @param mixed $value
* @return mixed
*/
public function cast($value)
{
switch ($this->type) {
case self::INT:
return (int)$value;
break;

case self::FLOAT:
return (float)$value;
break;

case self::STRING:
return (string)$value;
break;

default:
return $value;
break;
}
}

/**
* @inheritDoc
*/
public function __toString(): string
{
return $this->type;
}

private static function prepareType(string $type): string
{
$type = strtolower($type);

if (strpos($type, 'resource') === 0) {
$type = self::RESOURCE;
}

$map = [
'boolean' => self::BOOL,
'integer' => self::INT,
'double' => self::FLOAT,
];

return $map[$type] ?? $type;
}

/**
* @inheritDoc
*/
public static function supports(string $type): bool
{
$type = self::prepareType($type);

$supported = [
self::INT => true,
self::FLOAT => true,
self::STRING => true,
self::BOOL => true,
self::RESOURCE => true,
self::OBJECT => true,
self::ARRAY => true,
self::NULL => true,
self::CALLABLE => true,
self::ITERABLE => true,
];

return array_key_exists($type, $supported);
}

/**
* @inheritDoc
*/
public static function create(string $type, bool $strict = true): Type
{
return new self($type, $strict);
}
}
157 changes: 157 additions & 0 deletions src/CollectionType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

declare(strict_types=1);

namespace spaceonfire\Type;

use InvalidArgumentException;
use Traversable;

final class CollectionType implements Type
{
/**
* @var Type
*/
private $valueType;
/**
* @var Type|null
*/
private $keyType;
/**
* @var Type
*/
private $iterableType;

/**
* CollectionType constructor.
* @param Type $valueType
* @param Type|null $keyType
* @param Type|null $iterableType
*/
public function __construct(Type $valueType, ?Type $keyType = null, ?Type $iterableType = null)
{
$this->valueType = $valueType;
$this->keyType = $keyType;
$this->iterableType = $iterableType ?? new BuiltinType(BuiltinType::ITERABLE);
}

/**
* @inheritDoc
*/
public function check($value): bool
{
if (!$this->iterableType->check($value)) {
return false;
}

foreach ($value as $k => $v) {
if (!$this->valueType->check($v)) {
return false;
}

if ($this->keyType !== null && !$this->keyType->check($k)) {
return false;
}
}

return true;
}

/**
* @inheritDoc
*/
public function __toString(): string
{
if ($this->iterableType instanceof InstanceOfType || $this->keyType !== null) {
return $this->iterableType . '<' . implode(',', array_filter([$this->keyType, $this->valueType])) . '>';
}

return $this->valueType . '[]';
}

/**
* @inheritDoc
*/
public static function supports(string $type): bool
{
$typeParts = self::parseType($type);

if ($typeParts === null) {
return false;
}

if (isset($typeParts['iterable'])) {
if (InstanceOfType::supports($typeParts['iterable'])) {
if (
$typeParts['iterable'] !== Traversable::class &&
!is_subclass_of($typeParts['iterable'], Traversable::class)
) {
return false;
}
} else {
if (!in_array($typeParts['iterable'], ['array', 'iterable'], true)) {
return false;
}
}
}

if (!isset($typeParts['value'])) {
return false;
}

return true;
}

/**
* @inheritDoc
*/
public static function create(string $type): Type
{
if (!self::supports($type)) {
throw new InvalidArgumentException(sprintf('Type "%s" is not supported by %s', $type, __CLASS__));
}

$parsed = self::parseType($type);

$parsed['value'] = TypeFactory::create($parsed['value']);
$parsed['key'] = $parsed['key'] ? TypeFactory::create($parsed['key']) : null;
$parsed['iterable'] = $parsed['iterable'] ? TypeFactory::create($parsed['iterable']) : null;

return new self($parsed['value'], $parsed['key'], $parsed['iterable']);
}

private static function parseType(string $type): ?array
{
$result = [
'iterable' => null,
'key' => null,
'value' => null,
];

if (strpos($type, '[]') === strlen($type) - 2) {
$result['value'] = substr($type, 0, -2) ?: null;
return $result;
}

if (0 < ($openPos = strpos($type, '<')) && strpos($type, '>') === strlen($type) - 1) {
$result['iterable'] = substr($type, 0, $openPos);
[$key, $value] = array_map('trim', explode(',', substr($type, $openPos + 1, -1))) + [null, null];

if (!$value && !$key) {
return null;
}

if ($value === null) {
$value = $key;
$key = null;
}

$result['key'] = $key ?: null;
$result['value'] = $value ?: null;

return $result;
}

return null;
}
}
Loading

0 comments on commit e467f15

Please sign in to comment.