Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <michael@laravel.software>
*
* @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();
}
}