Skip to content

Commit 5bd29b8

Browse files
author
Daniel Gomes
committed
Initial Commit
0 parents  commit 5bd29b8

21 files changed

+1263
-0
lines changed

.gitignore

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

LICENSE

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

README.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Minimalist and Typed Immutable Collections for PHP
2+
3+
A library that provides a set of minimalist, typed and piped Immutable Collections for PHP.
4+
5+
#### What Problems does this solve?
6+
7+
* Immutable List Collection
8+
* Immutable Set Collection
9+
10+
#### Why Typed Immutable Collections
11+
12+
###### Typed Collections
13+
14+
Since PHP does not have [Generics](https://en.wikipedia.org/wiki/Generics_in_Java) like Java has, it's not possible to have native typed collections.
15+
The Collections available in this Library are the foundation for you to create your own Typed Collections, you just need to extend them.
16+
17+
###### Typed Immutable Collections for [DDD (Domain Driven Design)](https://en.wikipedia.org/wiki/Domain-driven_design)
18+
19+
DDD is all about having your code speaking the business language, the called Ubiquitous Language. Without Collections in PHP this is very hard to achieve using `Arrays` because you cannot add behavior to them. So what usually happens you add that behavior to your entity but it shouldn't be there. Another problem is the mutability of `Arrays`, `VOs` (Value Objects) **MUST** be always **immutable** and that's impossible with `Arrays` and you should always guarantee that the `elements` of that `Array` are all of the same type.
20+
21+
#### Collections vs [Arrays](https://secure.php.net/manual/pt_BR/language.types.array.php)
22+
23+
* `Collections` and `Arrays` both represent a Group of Elements.
24+
* `Collections` are not natively supported by PHP while `Arrays` are.
25+
* `Arrays` is THE data structure of PHP and is used for almost everything.
26+
* `Arrays` don't allow you to add new behavior while `Collections` allow it.
27+
* `Arrays` don't allow you to define a **type** for its elements while `Collections` allow it.
28+
29+
#### Other PHP Collections
30+
31+
There are other Collections for PHP out there, naming a few we have [Doctrine Collections](https://github.com/doctrine/collections/tree/master/lib/Doctrine/Common/Collections) and [Illuminate Collections](https://github.com/illuminate/support/blob/master/Collection.php).
32+
Those collections they solve different problems, are tailored to their specific use case, and their APIs are extensive and more important those Collections are Mutable.
33+
These combinations make it difficult to use those Collections for more simple use cases.
34+
That's why the Collections we provide here, have a very small API and don't even expose the Iterator API.
35+
This way you have the possibility to use them and extend it's behavior tailored for your needs.
36+
37+
#### Features
38+
39+
* Static construction for pipe usage.
40+
* Util methods like `isEmpty`, `count`, `toArray`, `contains`, `get` ,`map`, `filter`, `slice`, `merge`, `reverse`, `reduce`, `first`, `last`, `head`, `tail`.
41+
42+
#### Basic usage
43+
44+
###### Creating a Typed List Collection
45+
46+
```php
47+
<?php
48+
49+
use DCSG\ImmutableCollections\Collection;
50+
51+
final class MyStringCollection extends Collection {
52+
// We have to override the `validateItems` in order to ensure the right Type for the Collection.
53+
protected function validateItems(array $elements): void
54+
{
55+
foreach ($elements as $element) {
56+
if (!\is_string($element)) {
57+
throw new InvalidArgumentException('Element is not a String.');
58+
}
59+
}
60+
}
61+
}
62+
63+
$collection = MyStringCollection::create(['foo', 'bar']);
64+
echo $collection->count(); // 2
65+
```
66+
67+
###### Creating a Typed Set Collection
68+
69+
```php
70+
<?php
71+
72+
use DCSG\ImmutableCollections\SetCollection;
73+
74+
final class MyStringSetCollection extends SetCollection {
75+
// We have to override the `validateItems` in order to ensure the right Type for the Collection.
76+
protected function validateItems(array $elements): void
77+
{
78+
foreach ($elements as $element) {
79+
if (!\is_string($element)) {
80+
throw new InvalidArgumentException('Element is not a String.');
81+
}
82+
}
83+
}
84+
}
85+
86+
$collection = MyStringSetCollection::create(['foo', 'bar']);
87+
echo $collection->count(); // 2
88+
89+
$collection = MyStringSetCollection::create(['foo', 'bar', 'foo']); // Throws InvalidArgumentException
90+
```
91+
92+
## Examples
93+
94+
We provide two simple examples for better understanding. One related to [invoices](https://github.com/dcsg/php-immutable-collections/blob/master/examples/Invoices/app.php) and another one regarding [Legs of a Cargo Ship](https://github.com/dcsg/php-immutable-collections/blob/master/examples/CargoLegs/app.php).

composer.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "dcsg/php-immutable-collections",
3+
"type": "library",
4+
"description": "Minimalist and Typed Immutable Collections",
5+
"keywords": [
6+
"collection",
7+
"immutable",
8+
"immutable-collection",
9+
"pipe"
10+
],
11+
"license": "MIT",
12+
"authors": [
13+
{
14+
"name": "Daniel Gomes",
15+
"email": "[email protected]",
16+
"homepage": "https://dcsg.me"
17+
}
18+
],
19+
"require": {
20+
"php": "^7.1"
21+
},
22+
"require-dev": {
23+
"phpunit/phpunit": "^7"
24+
},
25+
"autoload": {
26+
"psr-4": {
27+
"DCSG\\ImmutableCollections\\": "src",
28+
"Test\\DCSG\\ImmutableCollections\\": "tests"
29+
}
30+
},
31+
"scripts": {
32+
"test": [
33+
"./vendor/bin/phpunit"
34+
]
35+
}
36+
}

examples/CargoLegs/Leg.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Examples\DCSG\ImmutableCollections\CargoLegs;
4+
5+
final class Leg
6+
{
7+
/** @var string */
8+
private $from;
9+
10+
/** @var string */
11+
private $to;
12+
13+
/** @var float */
14+
private $distance;
15+
16+
public function __construct(string $from, string $to, float $distance)
17+
{
18+
$this->from = $from;
19+
$this->to = $to;
20+
$this->distance = $distance;
21+
}
22+
23+
public function from(): string
24+
{
25+
return $this->from;
26+
}
27+
28+
public function to(): string
29+
{
30+
return $this->to;
31+
}
32+
33+
/**
34+
* @return float
35+
*/
36+
public function distance(): float
37+
{
38+
return $this->distance;
39+
}
40+
41+
public function __toString(): string
42+
{
43+
return "From: {$this->from()}\tTo: {$this->to()}\tDistance: {$this->distance()}";
44+
}
45+
}

examples/CargoLegs/Legs.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace Examples\DCSG\ImmutableCollections\CargoLegs;
4+
5+
use InvalidArgumentException;
6+
use DCSG\ImmutableCollections\Collection;
7+
8+
/**
9+
* @method Leg first()
10+
* @method Leg last()
11+
* @method Leg head()
12+
* @method Leg[] getIterator(): CollectionIterator
13+
*/
14+
final class Legs extends Collection
15+
{
16+
public function from(string $location): Legs
17+
{
18+
return $this->filter(function (Leg $leg) use ($location) {
19+
return $leg->from() === $location;
20+
});
21+
}
22+
23+
public function to(string $location): Legs
24+
{
25+
return $this->filter(function (Leg $leg) use ($location) {
26+
return $leg->to() === $location;
27+
});
28+
}
29+
30+
public function totalDistance(): float
31+
{
32+
return (float)$this->reduce(function (?float $total, Leg $leg): float {
33+
return $total + $leg->distance();
34+
});
35+
}
36+
37+
public function printLog(): void
38+
{
39+
foreach ($this as $leg) {
40+
echo "{$leg}\n";
41+
}
42+
}
43+
44+
/**
45+
* @param Leg[] $legs
46+
*/
47+
protected function validateItems(array $legs): void
48+
{
49+
foreach ($legs as $leg) {
50+
if (!$leg instanceof Leg) {
51+
throw new InvalidArgumentException('Element is not an instance of Leg');
52+
}
53+
}
54+
}
55+
}

examples/CargoLegs/app.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
$loader = require __DIR__ . '/../../vendor/autoload.php';
4+
$loader->addPsr4('Examples\\DCSG\\ImmutableCollections\\CargoLegs\\', __DIR__);
5+
6+
use Examples\DCSG\ImmutableCollections\CargoLegs\Leg;
7+
use Examples\DCSG\ImmutableCollections\CargoLegs\Legs;
8+
9+
$legs = Legs::create([
10+
new Leg('Lisbon', 'Porto', 240),
11+
new Leg('Lisbon', 'Aveiro', 200),
12+
new Leg('Lisbon', 'Setubal', 40),
13+
new Leg('Porto', 'Setubal', 280),
14+
new Leg('Porto', 'Lisbon', 240),
15+
]);
16+
17+
$totalDistance = $legs->totalDistance();
18+
19+
echo "Total distance: {$totalDistance}km\n";
20+
echo "# Legs from Lisbon: {$legs->from('Lisbon')->count()}\n";
21+
echo "# Legs to Setubal: {$legs->to('Setubal')->count()}\n";
22+
echo "# Legs from Porto: {$legs->from('Porto')->count()}\n";
23+
echo "# Legs to Porto: {$legs->to('Porto')->count()}\n";
24+
25+
echo "\n## Log\n";
26+
$legs->printLog();

examples/Invoices/Invoice.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace Examples\DCSG\ImmutableCollections\Invoices;
4+
5+
final class Invoice
6+
{
7+
/** @var string */
8+
private $id;
9+
/** @var int */
10+
private $number;
11+
/** @var int */
12+
private $year;
13+
/** @var InvoiceItems */
14+
private $items;
15+
/** @var bool */
16+
private $paid;
17+
18+
public function __construct(string $id, int $number, int $year, InvoiceItems $items)
19+
{
20+
$this->id = $id;
21+
$this->number = $number;
22+
$this->year = $year;
23+
$this->items = $items;
24+
$this->paid = false;
25+
}
26+
27+
public function pay(): void
28+
{
29+
$this->paid = true;
30+
}
31+
32+
public function isPaid(): bool
33+
{
34+
return $this->paid;
35+
}
36+
37+
public function totalIncludingVAT(): float
38+
{
39+
return $this->items()->totalIncludingVAT();
40+
}
41+
42+
public function items(): InvoiceItems
43+
{
44+
return $this->items;
45+
}
46+
47+
public function totalExcludingVAT(): float
48+
{
49+
return $this->items()->totalExcludingVAT();
50+
}
51+
52+
public function __toString(): string
53+
{
54+
$paid = $this->isPaid() ? 'Yes' : 'No';
55+
56+
return "Invoice # {$this->number}/{$this->year}\tPaid: {$paid}\tTotal excl. VAT: {$this->totalExcludingVAT()}\tTotal incl. VAT: {$this->totalIncludingVAT()}";
57+
}
58+
}

0 commit comments

Comments
 (0)