Skip to content

Commit

Permalink
Add Url value object (#39)
Browse files Browse the repository at this point in the history
* Add `Url` value object.

* Missing assertion 🧪

* Update docs 📚

* Drop support of stringable in `URL`

* Year.
  • Loading branch information
michael-rubel authored Jun 15, 2023
1 parent 9f60de5 commit d94bc7f
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 2 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ composer require michael-rubel/laravel-value-objects
## Built-in value objects

- [`Boolean`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Boolean.php)
- [`Number`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Number.php)
- [`Text`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Text.php)
- [`ClassString`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/ClassString.php)
- [`Email`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Email.php)
- [`FullName`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/FullName.php)
- [`Name`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Name.php)
- [`Number`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Number.php)
- [`Phone`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Phone.php)
- [`TaxNumber`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/TaxNumber.php)
- [`Text`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Primitive/Text.php)
- [`Url`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Url.php)
- [`Uuid`](https://github.com/michael-rubel/laravel-value-objects/blob/main/src/Collection/Complex/Uuid.php)

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

---

### Url
```php
$uuid = new Url('my-blog-page');
$uuid = Url::make('my-blog-page');
$uuid = Url::from('my-blog-page');

$uuid->value(); // 'https://example.com/my-blog-page'
(string) $uuid; // 'https://example.com/my-blog-page'
$uuid->toArray(); // ['https://example.com/my-blog-page']
```

---

### Uuid
```php
$uuid = new Uuid('8547d10c-7a37-492a-8d33-be0e5ae6119b', 'Optional name');
Expand Down
65 changes: 65 additions & 0 deletions src/Collection/Complex/Url.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

/**
* This file is part of michael-rubel/laravel-value-objects. (https://github.com/michael-rubel/laravel-value-objects)
*
* @link https://github.com/michael-rubel/laravel-value-objects for the canonical source repository
* @copyright Copyright (c) 2023 Michael Rubél. (https://github.com/michael-rubel/)
* @license https://raw.githubusercontent.com/michael-rubel/laravel-value-objects/main/LICENSE.md MIT
*/

namespace MichaelRubel\ValueObjects\Collection\Complex;

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use MichaelRubel\ValueObjects\Collection\Primitive\Text;

/**
* "Url" object presenting a URL.
*
* @author Michael Rubél <[email protected]>
*
* @template TKey of array-key
* @template TValue
*
* @method static static make(string $value)
* @method static static from(string $value)
* @method static static makeOrNull(string|null $value)
*
* @extends Text<TKey, TValue>
*/
class Url extends Text
{
/**
* Create a new instance of the value object.
*
* @param string $value
*/
public function __construct(string $value)
{
parent::__construct($value);

$this->value = url($value);

$validator = Validator::make(
['url' => $this->value()],
['url' => $this->validationRules()],
);

if ($validator->fails()) {
throw ValidationException::withMessages([__('Your URL is invalid.')]);
}
}

/**
* Define the rules for email validator.
*
* @return array
*/
protected function validationRules(): array
{
return ['required', 'url'];
}
}
141 changes: 141 additions & 0 deletions tests/Unit/Complex/UrlTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

declare(strict_types=1);

use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Stringable;
use Illuminate\Validation\ValidationException;
use MichaelRubel\ValueObjects\Collection\Complex\Url;

test('can instantiate valid url', function () {
$url = new Url('test-url');
$this->assertSame('http://localhost/test-url', $url->value());
});

test('can url accepts query string', function () {
$url = new Url('test-url?query=test&string=test2');
$this->assertSame('http://localhost/test-url?query=test&string=test2', $url->value());
});

test('can url accepts full url', function () {
$url = new Url('https://example.com/test-url?query=test&string=test2');
$this->assertSame('https://example.com/test-url?query=test&string=test2', $url->value());
});

test('cannot instantiate invalid url', function () {
$this->expectException(ValidationException::class);

new Url(' Test Url ');
});

test('cannot instantiate invalid url with try/catch', function () {
try {
new Url(' Test Url ');
} catch (ValidationException $exception) {
$this->assertSame('Your URL is invalid.', $exception->getMessage());
}
});

test('can cast url to string', function () {
$url = new Url('test-url');
$this->assertSame('http://localhost/test-url', (string) $url);
});

test('url cannot accept null', function () {
$this->expectException(\TypeError::class);

new Url(null);
});

test('url fails when no argument passed', function () {
$this->expectException(\TypeError::class);

new Url();
});

test('url fails when empty string passed', function () {
$this->expectException(\InvalidArgumentException::class);

new Url('');
});

test('url is makeable', function () {
$valueObject = Url::make('1');
$this->assertSame('http://localhost/1', $valueObject->value());
});

test('url is macroable', function () {
Url::macro('str', function () {
return str($this->value());
});

$valueObject = new Url('test-url');

$this->assertTrue($valueObject->str()->is('http://localhost/test-url'));
});

test('url is conditionable', function () {
$valueObject = new Url('1');
$this->assertSame('http://localhost/1', $valueObject->when(true)->value());
$this->assertSame($valueObject, $valueObject->when(false)->value());
});

test('url is arrayable', function () {
$array = (new Url('test-url'))->toArray();
$this->assertSame(['http://localhost/test-url'], $array);
});

test('url is stringable', function () {
$valueObject = new Url('test-url');
$this->assertSame('http://localhost/test-url', (string) $valueObject);

$valueObject = new Url('test-url');
$this->assertSame('http://localhost/test-url', $valueObject->toString());
});

test('url has immutable properties', function () {
$this->expectException(\InvalidArgumentException::class);
$valueObject = new Url('lorem-ipsum');
$this->assertSame('http://localhost/lorem-ipsum', $valueObject->value);
$valueObject->value = 'immutable';
});

test('url has immutable constructor', function () {
$this->expectException(\InvalidArgumentException::class);
$valueObject = new Url('test-url');
$valueObject->__construct(' Lorem ipsum ');
});

test('can extend protected methods in url', function () {
$email = new TestUrl('test-url');
$this->assertSame(['required', 'url'], $email->validationRules());
});

class TestUrl extends Url
{
/**
* Create a new instance of the value object.
*
* @param string|Stringable $value
*/
public function __construct(string|Stringable $value)
{
parent::__construct($value);

$this->value = url($value);

$validator = Validator::make(
['url' => $this->value()],
['url' => $this->validationRules()],
);

if ($validator->fails()) {
throw ValidationException::withMessages([__('Your URL is invalid.')]);
}
}

public function validationRules(): array
{
return parent::validationRules();
}
}

0 comments on commit d94bc7f

Please sign in to comment.