Skip to content

Commit d94bc7f

Browse files
Add Url value object (#39)
* Add `Url` value object. * Missing assertion 🧪 * Update docs 📚 * Drop support of stringable in `URL` * Year.
1 parent 9f60de5 commit d94bc7f

File tree

3 files changed

+222
-2
lines changed

3 files changed

+222
-2
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ composer require michael-rubel/laravel-value-objects
2626
## Built-in value objects
2727

2828
- [`Boolean`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Boolean.php)
29-
- [`Number`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Number.php)
30-
- [`Text`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Text.php)
3129
- [`ClassString`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/ClassString.php)
3230
- [`Email`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Email.php)
3331
- [`FullName`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/FullName.php)
3432
- [`Name`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Name.php)
33+
- [`Number`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Number.php)
3534
- [`Phone`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Phone.php)
3635
- [`TaxNumber`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/TaxNumber.php)
36+
- [`Text`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Text.php)
37+
- [`Url`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Url.php)
3738
- [`Uuid`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Uuid.php)
3839

3940
### Artisan command
@@ -198,6 +199,19 @@ $taxNumber->prefix(); // 'PL'
198199

199200
---
200201

202+
### Url
203+
```php
204+
$uuid = new Url('my-blog-page');
205+
$uuid = Url::make('my-blog-page');
206+
$uuid = Url::from('my-blog-page');
207+
208+
$uuid->value(); // 'https://example.com/my-blog-page'
209+
(string) $uuid; // 'https://example.com/my-blog-page'
210+
$uuid->toArray(); // ['https://example.com/my-blog-page']
211+
```
212+
213+
---
214+
201215
### Uuid
202216
```php
203217
$uuid = new Uuid('8547d10c-7a37-492a-8d33-be0e5ae6119b', 'Optional name');

src/Collection/Complex/Url.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of michael-rubel/laravel-value-objects. (https://github.com/michael-rubel/laravel-value-objects)
7+
*
8+
* @link https://github.com/michael-rubel/laravel-value-objects for the canonical source repository
9+
* @copyright Copyright (c) 2023 Michael Rubél. (https://github.com/michael-rubel/)
10+
* @license https://raw.githubusercontent.com/michael-rubel/laravel-value-objects/main/LICENSE.md MIT
11+
*/
12+
13+
namespace MichaelRubel\ValueObjects\Collection\Complex;
14+
15+
use Illuminate\Support\Facades\Validator;
16+
use Illuminate\Validation\ValidationException;
17+
use MichaelRubel\ValueObjects\Collection\Primitive\Text;
18+
19+
/**
20+
* "Url" object presenting a URL.
21+
*
22+
* @author Michael Rubél <michael@laravel.software>
23+
*
24+
* @template TKey of array-key
25+
* @template TValue
26+
*
27+
* @method static static make(string $value)
28+
* @method static static from(string $value)
29+
* @method static static makeOrNull(string|null $value)
30+
*
31+
* @extends Text<TKey, TValue>
32+
*/
33+
class Url extends Text
34+
{
35+
/**
36+
* Create a new instance of the value object.
37+
*
38+
* @param string $value
39+
*/
40+
public function __construct(string $value)
41+
{
42+
parent::__construct($value);
43+
44+
$this->value = url($value);
45+
46+
$validator = Validator::make(
47+
['url' => $this->value()],
48+
['url' => $this->validationRules()],
49+
);
50+
51+
if ($validator->fails()) {
52+
throw ValidationException::withMessages([__('Your URL is invalid.')]);
53+
}
54+
}
55+
56+
/**
57+
* Define the rules for email validator.
58+
*
59+
* @return array
60+
*/
61+
protected function validationRules(): array
62+
{
63+
return ['required', 'url'];
64+
}
65+
}

tests/Unit/Complex/UrlTest.php

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Support\Facades\Validator;
6+
use Illuminate\Support\Stringable;
7+
use Illuminate\Validation\ValidationException;
8+
use MichaelRubel\ValueObjects\Collection\Complex\Url;
9+
10+
test('can instantiate valid url', function () {
11+
$url = new Url('test-url');
12+
$this->assertSame('http://localhost/test-url', $url->value());
13+
});
14+
15+
test('can url accepts query string', function () {
16+
$url = new Url('test-url?query=test&string=test2');
17+
$this->assertSame('http://localhost/test-url?query=test&string=test2', $url->value());
18+
});
19+
20+
test('can url accepts full url', function () {
21+
$url = new Url('https://example.com/test-url?query=test&string=test2');
22+
$this->assertSame('https://example.com/test-url?query=test&string=test2', $url->value());
23+
});
24+
25+
test('cannot instantiate invalid url', function () {
26+
$this->expectException(ValidationException::class);
27+
28+
new Url(' Test Url ');
29+
});
30+
31+
test('cannot instantiate invalid url with try/catch', function () {
32+
try {
33+
new Url(' Test Url ');
34+
} catch (ValidationException $exception) {
35+
$this->assertSame('Your URL is invalid.', $exception->getMessage());
36+
}
37+
});
38+
39+
test('can cast url to string', function () {
40+
$url = new Url('test-url');
41+
$this->assertSame('http://localhost/test-url', (string) $url);
42+
});
43+
44+
test('url cannot accept null', function () {
45+
$this->expectException(\TypeError::class);
46+
47+
new Url(null);
48+
});
49+
50+
test('url fails when no argument passed', function () {
51+
$this->expectException(\TypeError::class);
52+
53+
new Url();
54+
});
55+
56+
test('url fails when empty string passed', function () {
57+
$this->expectException(\InvalidArgumentException::class);
58+
59+
new Url('');
60+
});
61+
62+
test('url is makeable', function () {
63+
$valueObject = Url::make('1');
64+
$this->assertSame('http://localhost/1', $valueObject->value());
65+
});
66+
67+
test('url is macroable', function () {
68+
Url::macro('str', function () {
69+
return str($this->value());
70+
});
71+
72+
$valueObject = new Url('test-url');
73+
74+
$this->assertTrue($valueObject->str()->is('http://localhost/test-url'));
75+
});
76+
77+
test('url is conditionable', function () {
78+
$valueObject = new Url('1');
79+
$this->assertSame('http://localhost/1', $valueObject->when(true)->value());
80+
$this->assertSame($valueObject, $valueObject->when(false)->value());
81+
});
82+
83+
test('url is arrayable', function () {
84+
$array = (new Url('test-url'))->toArray();
85+
$this->assertSame(['http://localhost/test-url'], $array);
86+
});
87+
88+
test('url is stringable', function () {
89+
$valueObject = new Url('test-url');
90+
$this->assertSame('http://localhost/test-url', (string) $valueObject);
91+
92+
$valueObject = new Url('test-url');
93+
$this->assertSame('http://localhost/test-url', $valueObject->toString());
94+
});
95+
96+
test('url has immutable properties', function () {
97+
$this->expectException(\InvalidArgumentException::class);
98+
$valueObject = new Url('lorem-ipsum');
99+
$this->assertSame('http://localhost/lorem-ipsum', $valueObject->value);
100+
$valueObject->value = 'immutable';
101+
});
102+
103+
test('url has immutable constructor', function () {
104+
$this->expectException(\InvalidArgumentException::class);
105+
$valueObject = new Url('test-url');
106+
$valueObject->__construct(' Lorem ipsum ');
107+
});
108+
109+
test('can extend protected methods in url', function () {
110+
$email = new TestUrl('test-url');
111+
$this->assertSame(['required', 'url'], $email->validationRules());
112+
});
113+
114+
class TestUrl extends Url
115+
{
116+
/**
117+
* Create a new instance of the value object.
118+
*
119+
* @param string|Stringable $value
120+
*/
121+
public function __construct(string|Stringable $value)
122+
{
123+
parent::__construct($value);
124+
125+
$this->value = url($value);
126+
127+
$validator = Validator::make(
128+
['url' => $this->value()],
129+
['url' => $this->validationRules()],
130+
);
131+
132+
if ($validator->fails()) {
133+
throw ValidationException::withMessages([__('Your URL is invalid.')]);
134+
}
135+
}
136+
137+
public function validationRules(): array
138+
{
139+
return parent::validationRules();
140+
}
141+
}

0 commit comments

Comments
 (0)