Skip to content

Commit

Permalink
added basic generic collections and onlyGenerics filter
Browse files Browse the repository at this point in the history
  • Loading branch information
henzeb committed Jun 11, 2023
1 parent 1815438 commit fa974af
Show file tree
Hide file tree
Showing 34 changed files with 868 additions and 75 deletions.
92 changes: 86 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,56 @@ composer require henzeb/laravel-typed-collection
````php
use Henzeb\Collection\Enums\Type;

$collection = collect()->withGenerics(Type::String, Post::class);
$collection = collect()
->withGenerics(Type::String, Post::class);

$collection->add('Hello World'); // succeeds
$collection->put('post',new Post()); // succeeds
$collection[] = new User(); // fails
$collection[] = new User(); // throws InvalidTypeException
````

### The accepts method

When a type is added to a typed collection that is not accepted,
An exception is thrown. With `accepts`, you can test your value
before adding it to the collection.

````php

$collection = collect()
->withGenerics(Type::String);

$value = 'Hello World';
if($collection->accepts($value)) {
$collection->add($value); // added
}

$value = true;
if($collection->accepts($value)) {
$collection->add($value); // does not get added
}

````

### Filtering a collection

with `onlyGenerics` you can filter out any value that doesn´t
match the generic type. this can be handy when you want to pipe
the results into a `TypedCollection`.

````php
use Henzeb\Collection\Enums\Type;

$collection = collect(['Hello world', new Post()])
->onlyGenerics(Type::String);

$collection->all(); //returns ['Hello world'];

$collection = collect(['Hello world', true, new User()])
->lazy()
->onlyGenerics(Type::String, Type::Bool);

$collection->all(); //returns ['Hello world', true];
````

### Extending TypedCollection
Expand Down Expand Up @@ -83,14 +128,49 @@ Note: be aware the value type is validated when yielded and not before or after.
### Get Lazy Typed Collection from Typed Collection

Using the `lazy()` method you will receive the default `LazyCollection`,
If that's not what you want, you can do the following
If that's not what you want, you specify the lazyClass

### Available Types
````php
class PostCollection extends TypedCollection
{
protected function generics(): string|Type|array
{
return Post::class;
}

protected function lazyClass(): string
{
return LazyPostCollection::class;
}
}
````

That way, you will always receive a lazy typed collection.

### Available types and collections

There is a `Type` enum which supports all types supported by PHP.
Next to `Type`, you can add in Fully Qualified Class Names of interfaces
or objects.

| Generic Type | Collection |
|----------------|-----------------------------------|
| Type::Bool | Henzeb\Collection\Typed\Booleans |
| Type::String | Henzeb\Collection\Typed\Strings |
| Type::Int | Henzeb\Collection\Typed\Integers |
| Type::Double | Henzeb\Collection\Typed\Doubles |
| Type::Numeric | Henzeb\Collection\Typed\Numerics |
| Type::Array | Henzeb\Collection\Typed\Arrays |
| Type::Null | - |
| Type::Resource | Henzeb\Collection\Typed\Resources |
| Type::Object | Henzeb\Collection\Typed\Objects |
| JSON | Henzeb\Collection\Typed\Jsons |
| Uuid | Henzeb\Collection\Typed\Uuid |

Note: Each available collection also has a lazy counterpart.
For `Type::Bool` for example this would be
`Henzeb\Collection\Lazy\Bools`

#### Custom Generic Types

Sometimes, you want to validate scalar types some more. For example `JSON`.
Expand All @@ -99,12 +179,12 @@ To achieve that, you can use the `GenericType` interface.
````php
use Henzeb\Collection\Contracts\GenericType;

readonly class JSON implements GenericType
readonly class Json implements GenericType
{
public static function matchesType(mixed $item): bool
{
/** json_validate is a poly-fill function ahead of php 8.3 */
return json_validate($item);
return is_string($item) && json_validate($item);
}
}
````
Expand Down
13 changes: 1 addition & 12 deletions infection.json5
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,7 @@
},
"mutators": {
"@default": true,
"ProtectedVisibility": false,
"TrueValue": {
"ignore": [
"Henzeb\\Pipeline\\Concerns\\HandlesPipe::parseMethodNameFromClosure"
]
},
"MethodCallRemoval": {
"ignore": [
"Henzeb\\Pipeline\\Concerns\\HandlesPipe::parseMethodNameFromClosure"
]
},

"PublicVisibility": false,
},
"logs": {
"html": "infection.html"
Expand Down
38 changes: 38 additions & 0 deletions src/Concerns/HandlesTypeOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Henzeb\Collection\Concerns;

use Henzeb\Collection\Enums\Type;

trait HandlesTypeOutput
{
private function getType(mixed $item): string
{
if (is_object($item)) {
return $item::class;
}

return Type::fromValue($item)->value();
}

private function typesToString(array $types): string
{
return implode(
', ',
array_map(
function (mixed $type) {
if (is_string($type)) {
return $type;
}

if ($type instanceof Type) {
return $type->value();
}

return 'unknown';
},
$types
)
);
}
}
10 changes: 10 additions & 0 deletions src/Concerns/HasGenerics.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,14 @@ private function matchesGeneric(mixed $type, mixed $item): bool
Type::fromValue($item)
);
}

public function accepts(mixed $item): bool
{
try {
$this->validateType($item);
} catch (InvalidTypeException) {
return false;
}
return true;
}
}
34 changes: 3 additions & 31 deletions src/Exceptions/InvalidTypeException.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
namespace Henzeb\Collection\Exceptions;

use Exception;
use Henzeb\Collection\Enums\Type;
use Henzeb\Collection\Concerns\HandlesTypeOutput;

class InvalidTypeException extends Exception
{
use HandlesTypeOutput;

public function __construct(string $class, mixed $item, array $types)
{
parent::__construct(
Expand All @@ -18,34 +20,4 @@ public function __construct(string $class, mixed $item, array $types)
)
);
}

private function getType(mixed $item): string
{
if (is_object($item)) {
return $item::class;
}

return Type::fromValue($item)->value();
}

private function typesToString(array $types): string
{
return implode(
', ',
array_map(
function (mixed $type) {
if (is_string($type)) {
return $type;
}

if ($type instanceof Type) {
return $type->value();
}

return 'unknown';
},
$types
)
);
}
}
36 changes: 36 additions & 0 deletions src/Generics/Json.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Henzeb\Collection\Generics;

use Henzeb\Collection\Contracts\GenericType;
use Throwable;

class Json implements GenericType
{
private static ?bool $native = null;

public static function matchesType(mixed $item): bool
{
return is_string($item) && self::validateJson($item);
}

private static function validateJson(mixed $item): bool
{
if (self::$native ??= function_exists('json_validate')) {
return json_validate($item);
}

try {
json_decode(
$item,
true,
512,
JSON_THROW_ON_ERROR
);
} catch (Throwable) {
return false;
}

return true;
}
}
14 changes: 14 additions & 0 deletions src/Generics/Uuid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Henzeb\Collection\Generics;

use Henzeb\Collection\Contracts\GenericType;
use Ramsey\Uuid\Uuid as RamseyUuid;

class Uuid implements GenericType
{
public static function matchesType(mixed $item): bool
{
return is_string($item) && RamseyUuid::isValid($item);
}
}
14 changes: 14 additions & 0 deletions src/Lazy/Arrays.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Henzeb\Collection\Lazy;

use Henzeb\Collection\Enums\Type;
use Henzeb\Collection\LazyTypedCollection;

class Arrays extends LazyTypedCollection
{
protected function generics(): Type
{
return Type::Array;
}
}
14 changes: 14 additions & 0 deletions src/Lazy/Booleans.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Henzeb\Collection\Lazy;

use Henzeb\Collection\Enums\Type;
use Henzeb\Collection\LazyTypedCollection;

class Booleans extends LazyTypedCollection
{
protected function generics(): Type
{
return Type::Bool;
}
}
14 changes: 14 additions & 0 deletions src/Lazy/Doubles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Henzeb\Collection\Lazy;

use Henzeb\Collection\Enums\Type;
use Henzeb\Collection\LazyTypedCollection;

class Doubles extends LazyTypedCollection
{
protected function generics(): Type
{
return Type::Double;
}
}
14 changes: 14 additions & 0 deletions src/Lazy/Integers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Henzeb\Collection\Lazy;

use Henzeb\Collection\Enums\Type;
use Henzeb\Collection\LazyTypedCollection;

class Integers extends LazyTypedCollection
{
protected function generics(): Type
{
return Type::Int;
}
}
14 changes: 14 additions & 0 deletions src/Lazy/Jsons.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Henzeb\Collection\Lazy;

use Henzeb\Collection\Generics\Json;
use Henzeb\Collection\LazyTypedCollection;

class Jsons extends LazyTypedCollection
{
protected function generics(): string
{
return Json::class;
}
}
14 changes: 14 additions & 0 deletions src/Lazy/Numerics.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Henzeb\Collection\Lazy;

use Henzeb\Collection\Enums\Type;
use Henzeb\Collection\LazyTypedCollection;

class Numerics extends LazyTypedCollection
{
protected function generics(): Type
{
return Type::Numeric;
}
}

0 comments on commit fa974af

Please sign in to comment.