Skip to content

Commit

Permalink
Add map method to the Set class.
Browse files Browse the repository at this point in the history
  • Loading branch information
jojo1981 committed Jan 15, 2020
1 parent dc1defb commit 2e6be2d
Show file tree
Hide file tree
Showing 6 changed files with 419 additions and 26 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ The set has the following convenient instance methods:
- isEqualType(TypeInterface $type): bool
- isEqual(Set $other): bool
- compare(Set $other): DifferenceResult
- map(callable $mapper, ?string $type = null): Set
- count(): int

The `\Jojo1981\TypedSet\Set` has a static method `createFromElements`.
Expand Down
76 changes: 72 additions & 4 deletions src/Exception/SetException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,66 @@
*/
namespace Jojo1981\TypedSet\Exception;

use Jojo1981\PhpTypes\ClassType;
use Jojo1981\PhpTypes\TypeInterface;

/**
* @package Jojo1981\TypedSet\Exception
*/
class SetException extends \DomainException
{
/**
* @return SetException
*/
public static function canNotCompareSetsOfDifferenceType(): SetException
{
return new static('Can not compare 2 sets of different types');
}

/**
* @param TypeInterface $expectedType
* @param TypeInterface $actualType
* @param null|string $prefixMessage
* @return SetException
*/
public static function dataIsNotOfExpectedType(
TypeInterface $expectedType,
TypeInterface $actualType,
?string $prefixMessage = null
): SetException
{
return new self(\sprintf(
$prefixMessage . 'Data is not %s: `%s`, but %s: `%s`',
$expectedType instanceof ClassType ? 'an instance of' : 'of type',
$expectedType->getName(),
$actualType instanceof ClassType ? 'an instance of' : 'of type',
$actualType->getName()
));
}

/**
* @param string $type
* @param null|\Exception $previous
* @return SetException
*/
public static function typeIsNotValid(string $type, ?\Exception $previous = null): SetException
public static function givenTypeIsNotValid(string $type, ?\Exception $previous = null): SetException
{
return new self(
'Given type: `' . $type . '` is not a valid primitive type and also not an existing class',
'Given type: `' . $type . '` is not a valid type and also not an existing class',
0,
$previous
);
}

/**
* @param string $type
* @param null|\Exception $previous
* @return SetException
*/
public static function determinedTypeIsNotValid(string $type, ?\Exception $previous = null): SetException
{
return new self(
'Determined type: `' . $type . '` is not a valid type and also not an existing class',
0,
$previous
);
Expand All @@ -37,11 +83,33 @@ public static function emptyElementsCanNotDetermineType(): SetException
}

/**
* @param \Exception $previous
* @return SetException
*/
public static function couldNotCreateTypeFromValue(\Exception $previous): SetException
public static function typeOmittedOnEmptySet(): SetException
{
return new self('Type can not be omitted on an empty Set');
}

/**
* @param null|\Exception $previous
* @return SetException
*/
public static function couldNotCreateTypeFromValue(?\Exception $previous = null): SetException
{
return new self('Could not create type from value', 0, $previous);
}

/**
* @param string $typeName
* @param null|\Exception $previous
* @return SetException
*/
public static function couldNotCreateTypeFromTypeName(string $typeName, ?\Exception $previous = null): SetException
{
return new self(
\sprintf('Could not create type from type name: `%s`', $typeName),
0,
$previous
);
}
}
88 changes: 72 additions & 16 deletions src/Set.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Set implements \Countable, \IteratorAggregate
*/
public function __construct(string $type, array $elements = [])
{
static::assertType($type);
static::assertGivenType($type);
$this->type = self::createTypeFromName($type);
$this->addAll($elements);
}
Expand Down Expand Up @@ -175,7 +175,7 @@ public function isEqual(Set $other): bool
public function compare(Set $other): DifferenceResult
{
if (!$this->isEqualType($other->type)) {
throw new SetException('Can not compare 2 sets of different types');
throw SetException::canNotCompareSetsOfDifferenceType();
}

$lhsElementsMissing = [];
Expand Down Expand Up @@ -210,6 +210,46 @@ public function compare(Set $other): DifferenceResult
);
}

/**
* Map this Set into new Set using the passed mapper callback. When type is omitted it will be determined by the
* first result the mapper function produces.
*
* @param callable $mapper
* @param null|string $type
* @throws SetException
* @return Set
*/
public function map(callable $mapper, ?string $type = null): Set
{
if (null === $type && $this->isEmpty()) {
throw SetException::typeOmittedOnEmptySet();
}

$typeObject = null;
if (null !== $type) {
static::assertGivenType($type);
$typeObject = self::createTypeFromName($type);
} else {
$typeObject = self::createTypeFromValue($mapper(\reset($this->elements)));
self::assertDeterminedType($typeObject->getName());
}

$newElements = [];
foreach ($this->elements as $element) {
$newElement = $mapper($element);
if (!$typeObject->isAssignableValue($newElement)) {
throw SetException::dataIsNotOfExpectedType(
$typeObject,
self::createTypeFromValue($newElement),
'Mapper is not returning a correct value. '
);
}
$newElements[] = $newElement;
}

return new Set($typeObject->getName(), $newElements);
}

/**
* @return int
*/
Expand All @@ -235,13 +275,7 @@ private function assertElementIsValid($element): void
{
if (!$this->type->isAssignableValue($element)) {
$otherType = self::createTypeFromValue($element);
throw new SetException(\sprintf(
'Data is not %s: `%s`, but %s: `%s`',
$this->type instanceof ClassType ? 'an instance of' : 'of type',
$this->type->getName(),
$otherType instanceof ClassType ? 'an instance of' : 'of type',
$otherType->getName()
));
throw SetException::dataIsNotOfExpectedType($this->type, $otherType);
}
}

Expand Down Expand Up @@ -296,13 +330,35 @@ public static function createFromElements(array $elements): Set
* @throws SetException
* @return void
*/
private static function assertType(string $type): void
private static function assertGivenType(string $type): void
{
if (\in_array(\strtolower($type), self::NOT_SUPPORTED_TYPES)) {
throw SetException::typeIsNotValid($type);
throw SetException::givenTypeIsNotValid($type);
}

self::createTypeFromName($type);
try {
self::createTypeFromName($type);
} catch (\Exception $exception) {
throw SetException::givenTypeIsNotValid($type, $exception);
}
}

/**
* @param string $type
* @throws SetException
* @return void
*/
private static function assertDeterminedType(string $type): void
{
if (\in_array(\strtolower($type), self::NOT_SUPPORTED_TYPES)) {
throw SetException::determinedTypeIsNotValid($type);
}

try {
self::createTypeFromName($type);
} catch (\Exception $exception) { // @codeCoverageIgnore
throw SetException::determinedTypeIsNotValid($type, $exception); // @codeCoverageIgnore
}
}

/**
Expand All @@ -320,16 +376,16 @@ private static function createTypeFromValue($value): TypeInterface
}

/**
* @param string $name
* @param string $typeName
* @throws SetException
* @return TypeInterface
*/
private static function createTypeFromName(string $name): TypeInterface
private static function createTypeFromName(string $typeName): TypeInterface
{
try {
return AbstractType::createFromTypeName($name);
return AbstractType::createFromTypeName($typeName);
} catch (TypeException $exception) {
throw SetException::typeIsNotValid($name, $exception);
throw SetException::couldNotCreateTypeFromTypeName($typeName, $exception);
}
}
}
64 changes: 64 additions & 0 deletions tests/DataProvider/SetExceptionDataProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php declare(strict_types=1);
/*
* This file is part of the jojo1981/typed-set package
*
* Copyright (c) 2020 Joost Nijhuis <jnijhuis81@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed in the root of the source code
*/
namespace Jojo1981\TypedSet\TestSuite\DataProvider;

use Jojo1981\PhpTypes\ClassType;
use Jojo1981\PhpTypes\Exception\TypeException;
use Jojo1981\PhpTypes\FloatType;
use Jojo1981\PhpTypes\IntegerType;
use Jojo1981\PhpTypes\ObjectType;
use Jojo1981\PhpTypes\StringType;
use Jojo1981\PhpTypes\Value\ClassName;
use Jojo1981\PhpTypes\Value\Exception\ValueException;

/**
* @package Jojo1981\TypedSet\TestSuite\DataProvider
*/
class SetExceptionDataProvider
{
/**
* @throws ValueException
* @throws TypeException
* @return array[]
*/
public function getTestDataForDataIsNotOfExpectedType(): array
{
return [
[
new StringType(),
new IntegerType(),
'Data is not of type: `string`, but of type: `int`'
],
[
new StringType(),
new IntegerType(),
'Pref! Data is not of type: `string`, but of type: `int`',
'Pref! '
],
[
new ObjectType(),
new FloatType(),
'Data is not of type: `object`, but of type: `float`'
],
[
new ClassType(new ClassName(__CLASS__)),
new FloatType(),
'Data is not an instance of: `\Jojo1981\TypedSet\TestSuite\DataProvider\SetExceptionDataProvider`, but of type: `float`',
null
],
[
new FloatType(),
new ClassType(new ClassName(__CLASS__)),
'Prefix. Data is not of type: `float`, but an instance of: `\Jojo1981\TypedSet\TestSuite\DataProvider\SetExceptionDataProvider`',
'Prefix. '
]
];
}
}
Loading

0 comments on commit 2e6be2d

Please sign in to comment.