Skip to content

Commit d7c6262

Browse files
committed
feat(validation): add url and disposableUrlDomain rules
1 parent 7a7b163 commit d7c6262

13 files changed

Lines changed: 1220 additions & 6 deletions

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mutation:
1010
@vendor/bin/infection --threads=4
1111

1212
benchmark:
13-
@vendor/bin/phpbench run --report=default --output=csv > benchmarks/bench_results.csv
13+
@vendor/bin/phpbench run --report=default --warmup=2 --output=csv > benchmarks/bench_results.csv
1414

1515
p99:
1616
@cd benchmarks && php analyze_bench.php

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ $dv->addTranslations([
230230
- [Custom Strategies](docs/CUSTOM_STRATEGIES.md) - Extend with your own rules
231231
- [Internationalization](docs/INTERNATIONALIZATION.md) - Multi-language error messages
232232
- [Error Handling](docs/ERROR_HANDLING.md) - Working with validation errors
233-
- [Validation Rules](docs/VALIDATIONS.md) - All 40+ built-in rules
233+
- [Validation Rules](docs/VALIDATIONS.md) - All 30+ built-in rules
234234

235235
**Examples:**
236236
- [API Request Validation](examples/api-request.php)

docs/VALIDATIONS.md

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Validation Rules Reference
22

3-
Complete reference of all **28** built-in validation rules.
3+
Complete reference of all **30** built-in validation rules.
44

55
## Table of Contents
66

@@ -9,7 +9,7 @@ Complete reference of all **28** built-in validation rules.
99
- [Date](#date) (1 rules)
1010
- [File](#file) (2 rules)
1111
- [Numeric](#numeric) (3 rules)
12-
- [String](#string) (12 rules)
12+
- [String](#string) (14 rules)
1313
- [Type](#type) (7 rules)
1414

1515
---
@@ -242,6 +242,63 @@ $verifier->field("test")->disposableEmail([])
242242

243243
</details>
244244

245+
<details>
246+
<summary><code>disposableUrlDomain</code> - Validates that a URL is not from a disposable/temporary domain (URL sh...</summary>
247+
248+
**Description:**
249+
250+
Validates that a URL is not from a disposable/temporary domain (URL shorteners, free hosting, etc.)
251+
252+
**Parameters:**
253+
254+
<table>
255+
<tr><th>Name</th><th>Type</th><th>Required</th><th>Description</th></tr>
256+
<tr><td><code>disposables</code></td><td><code>array</code></td><td align="center">✗</td><td>Array of disposable domain patterns<br><em>Example: <code>[]</code></em><br><em>Default: <code>[]</code></em></td></tr>
257+
</table>
258+
259+
**Usage:**
260+
261+
```php
262+
$verifier->field("website")->disposableUrlDomain
263+
```
264+
265+
```php
266+
$verifier->field("website")->disposableUrlDomain(["bit.ly", "tinyurl.com"])
267+
```
268+
269+
</details>
270+
271+
<details>
272+
<summary><code>url</code> - Validates that a value is a valid URL with configurable schemes and TL...</summary>
273+
274+
**Description:**
275+
276+
Validates that a value is a valid URL with configurable schemes and TLD requirement
277+
278+
**Parameters:**
279+
280+
<table>
281+
<tr><th>Name</th><th>Type</th><th>Required</th><th>Description</th></tr>
282+
<tr><td><code>schemes</code></td><td><code>array</code></td><td align="center">✗</td><td>Allowed URL schemes<br><em>Example: <code>["http","https"]</code></em><br><em>Default: <code>["http","https"]</code></em></td></tr>
283+
<tr><td><code>requireTld</code></td><td><code>bool</code></td><td align="center">✗</td><td>Require a top-level domain (.com|.org|.net|...)<br><em>Example: <code>true</code></em><br><em>Default: <code>true</code></em></td></tr>
284+
</table>
285+
286+
**Usage:**
287+
288+
```php
289+
$verifier->field("website")->url
290+
```
291+
292+
```php
293+
$verifier->field("api")->url(["http", "https", "ws", "wss"])
294+
```
295+
296+
```php
297+
$verifier->field("intranet")->url(["http"], requireTld: false)
298+
```
299+
300+
</details>
301+
245302
<details>
246303
<summary><code>containsNumber</code> - Validates that a string contains at least one digit</summary>
247304

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ <h2>📖 Quick Start</h2><pre><code class="language-php">use Gravity\DataVerify;
6969
// Get errors
7070
if (!$isValid) {
7171
$errors = $dv->getErrors();
72-
}</code></pre><h2>📚 Categories</h2><p>Browse <strong>28</strong> validation rules organized in <strong>7</strong> categories:</p><div class="category-list"><div class="category-item"><strong>Comparison</strong> (2 rules)</div><div class="category-item"><strong>Core</strong> (1 rules)</div><div class="category-item"><strong>Date</strong> (1 rules)</div><div class="category-item"><strong>File</strong> (2 rules)</div><div class="category-item"><strong>Numeric</strong> (3 rules)</div><div class="category-item"><strong>String</strong> (12 rules)</div><div class="category-item"><strong>Type</strong> (7 rules)</div></div><h2>🌳 Nested Objects (Subfields)</h2><p>Validate nested data structures with dot notation:</p><pre><code class="language-php">$data->user = (object)[
72+
}</code></pre><h2>📚 Categories</h2><p>Browse <strong>30</strong> validation rules organized in <strong>7</strong> categories:</p><div class="category-list"><div class="category-item"><strong>Comparison</strong> (2 rules)</div><div class="category-item"><strong>Core</strong> (1 rules)</div><div class="category-item"><strong>Date</strong> (1 rules)</div><div class="category-item"><strong>File</strong> (2 rules)</div><div class="category-item"><strong>Numeric</strong> (3 rules)</div><div class="category-item"><strong>String</strong> (14 rules)</div><div class="category-item"><strong>Type</strong> (7 rules)</div></div><h2>🌳 Nested Objects (Subfields)</h2><p>Validate nested data structures with dot notation:</p><pre><code class="language-php">$data->user = (object)[
7373
'name' => 'John',
7474
'email' => 'john@example.com',
7575
'address' => (object)[

docs/openapi.json

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"openapi": "3.1.0",
33
"info": {
44
"title": "DataVerify Validation Rules",
5-
"description": "Complete reference of 28 validation rules. See above for usage examples.",
5+
"description": "Complete reference of 30 validation rules. See above for usage examples.",
66
"version": "1.0.0"
77
},
88
"components": {
@@ -137,6 +137,46 @@
137137
}
138138
}
139139
},
140+
"disposableUrlDomain": {
141+
"type": "object",
142+
"title": "DisposableUrlDomain",
143+
"description": "Validates that a URL is not from a disposable/temporary domain (URL shorteners, free hosting, etc.)\n\n**Usage:**\n```php\n$verifier->field(\"website\")->disposableUrlDomain\n```\n```php\n$verifier->field(\"website\")->disposableUrlDomain([\"bit.ly\", \"tinyurl.com\"])\n```\n\n**Parameters:**\n- `disposables`: []",
144+
"x-category": "String",
145+
"properties": {
146+
"disposables": {
147+
"description": "Array of disposable domain patterns",
148+
"type": "array",
149+
"example": [],
150+
"default": []
151+
}
152+
}
153+
},
154+
"url": {
155+
"type": "object",
156+
"title": "Url",
157+
"description": "Validates that a value is a valid URL with configurable schemes and TLD requirement\n\n**Usage:**\n```php\n$verifier->field(\"website\")->url\n```\n```php\n$verifier->field(\"api\")->url([\"http\", \"https\", \"ws\", \"wss\"])\n```\n```php\n$verifier->field(\"intranet\")->url([\"http\"], requireTld: false)\n```\n\n**Parameters:**\n- `schemes`: [\"http\",\"https\"]\n- `requireTld`: true",
158+
"x-category": "String",
159+
"properties": {
160+
"schemes": {
161+
"description": "Allowed URL schemes",
162+
"type": "array",
163+
"example": [
164+
"http",
165+
"https"
166+
],
167+
"default": [
168+
"http",
169+
"https"
170+
]
171+
},
172+
"requireTld": {
173+
"description": "Require a top-level domain (.com|.org|.net|...)",
174+
"type": "boolean",
175+
"example": true,
176+
"default": true
177+
}
178+
}
179+
},
140180
"containsNumber": {
141181
"type": "object",
142182
"title": "ContainsNumber",

docs/schema.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,46 @@
133133
}
134134
}
135135
},
136+
"disposableUrlDomain": {
137+
"type": "object",
138+
"title": "DisposableUrlDomain",
139+
"description": "Validates that a URL is not from a disposable/temporary domain (URL shorteners, free hosting, etc.)\n\n**Usage:**\n```php\n$verifier->field(\"website\")->disposableUrlDomain\n```\n```php\n$verifier->field(\"website\")->disposableUrlDomain([\"bit.ly\", \"tinyurl.com\"])\n```\n\n**Parameters:**\n- `disposables`: []",
140+
"x-category": "String",
141+
"properties": {
142+
"disposables": {
143+
"description": "Array of disposable domain patterns",
144+
"type": "array",
145+
"example": [],
146+
"default": []
147+
}
148+
}
149+
},
150+
"url": {
151+
"type": "object",
152+
"title": "Url",
153+
"description": "Validates that a value is a valid URL with configurable schemes and TLD requirement\n\n**Usage:**\n```php\n$verifier->field(\"website\")->url\n```\n```php\n$verifier->field(\"api\")->url([\"http\", \"https\", \"ws\", \"wss\"])\n```\n```php\n$verifier->field(\"intranet\")->url([\"http\"], requireTld: false)\n```\n\n**Parameters:**\n- `schemes`: [\"http\",\"https\"]\n- `requireTld`: true",
154+
"x-category": "String",
155+
"properties": {
156+
"schemes": {
157+
"description": "Allowed URL schemes",
158+
"type": "array",
159+
"example": [
160+
"http",
161+
"https"
162+
],
163+
"default": [
164+
"http",
165+
"https"
166+
]
167+
},
168+
"requireTld": {
169+
"description": "Require a top-level domain (.com|.org|.net|...)",
170+
"type": "boolean",
171+
"example": true,
172+
"default": true
173+
}
174+
}
175+
},
136176
"containsNumber": {
137177
"type": "object",
138178
"title": "ContainsNumber",

docs/validations.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,59 @@
142142
}
143143
]
144144
},
145+
"disposableUrlDomain": {
146+
"name": "disposableUrlDomain",
147+
"description": "Validates that a URL is not from a disposable\/temporary domain (URL shorteners, free hosting, etc.)",
148+
"category": "String",
149+
"examples": [
150+
"$verifier->field(\"website\")->disposableUrlDomain",
151+
"$verifier->field(\"website\")->disposableUrlDomain([\"bit.ly\", \"tinyurl.com\"])"
152+
],
153+
"parameters": [
154+
{
155+
"name": "disposables",
156+
"type": "array",
157+
"required": false,
158+
"default": [],
159+
"description": "Array of disposable domain patterns",
160+
"example": []
161+
}
162+
]
163+
},
164+
"url": {
165+
"name": "url",
166+
"description": "Validates that a value is a valid URL with configurable schemes and TLD requirement",
167+
"category": "String",
168+
"examples": [
169+
"$verifier->field(\"website\")->url",
170+
"$verifier->field(\"api\")->url([\"http\", \"https\", \"ws\", \"wss\"])",
171+
"$verifier->field(\"intranet\")->url([\"http\"], requireTld: false)"
172+
],
173+
"parameters": [
174+
{
175+
"name": "schemes",
176+
"type": "array",
177+
"required": false,
178+
"default": [
179+
"http",
180+
"https"
181+
],
182+
"description": "Allowed URL schemes",
183+
"example": [
184+
"http",
185+
"https"
186+
]
187+
},
188+
{
189+
"name": "requireTld",
190+
"type": "bool",
191+
"required": false,
192+
"default": true,
193+
"description": "Require a top-level domain (.com|.org|.net|...)",
194+
"example": true
195+
}
196+
]
197+
},
145198
"containsNumber": {
146199
"name": "containsNumber",
147200
"description": "Validates that a string contains at least one digit",

docs/validations.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Available validations:
99
- containsUpper
1010
- date
1111
- disposableEmail
12+
- disposableUrlDomain
1213
- email
1314
- fileExists
1415
- fileMime
@@ -27,3 +28,4 @@ Available validations:
2728
- regex
2829
- required
2930
- string
31+
- url

src/DataVerify.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* @property DataVerify $containsUpper Validates that a string contains at least one uppercase letter
2626
* @property DataVerify $date Validates that a value is a valid date in the specified format. Performs strict validation
2727
* @property DataVerify $disposableEmail Validates that an email is not from a disposable domain
28+
* @property DataVerify $disposableUrlDomain Validates that a URL is not from a disposable/temporary domain (URL shorteners, free hosting, etc.)
2829
* @property DataVerify $email Validates that a value is a valid email address
2930
* @property DataVerify $fileExists Validates that a file exists at the given path
3031
* @property DataVerify $int Validates that a value is an integer. In strict mode (default), only true integers are accepted
@@ -35,12 +36,14 @@
3536
* @property DataVerify $object Validates that a value is an object
3637
* @property DataVerify $required Validates that a field is not empty. Objects are cast to arrays to check emptiness.
3738
* @property DataVerify $string Validates that a value is a string
39+
* @property DataVerify $url Validates that a value is a valid URL with configurable schemes and TLD requirement
3840
* @property DataVerify $then Activate conditional validation mode
3941
*
4042
* @method DataVerify between(DateTime|string|int|float $min, DateTime|string|int|float $max) Validates that a value is between two bounds. Supports numeric values and DateTime objects
4143
* @method DataVerify boolean(bool $strict = true) Validates that a value is a boolean. In strict mode (default), only true/false are accepted
4244
* @method DataVerify date(string $format = 'Y-m-d') Validates that a value is a valid date in the specified format. Performs strict validation
4345
* @method DataVerify disposableEmail(array $disposables = []) Validates that an email is not from a disposable domain
46+
* @method DataVerify disposableUrlDomain(array $disposables = []) Validates that a URL is not from a disposable/temporary domain (URL shorteners, free hosting, etc.)
4447
* @method DataVerify fileMime(array|string $mime) Validates that a file has an allowed MIME type
4548
* @method DataVerify greaterThan(mixed $min) Validates that a value is greater than a specified limit
4649
* @method DataVerify in(object|array $allowed) Validates that a value exists in an allowed list or as an object property
@@ -50,6 +53,7 @@
5053
* @method DataVerify minLength(int $min) Validates that a string has a minimum length
5154
* @method DataVerify notIn(object|array $forbidden) Validates that a value does not exist in a forbidden list or as an object property
5255
* @method DataVerify regex(string $pattern) Validates that a value matches a regular expression pattern. Warnings are suppressed
56+
* @method DataVerify url(array $schemes = [...], bool $requireTld = true) Validates that a value is a valid URL with configurable schemes and TLD requirement
5357
*/
5458
class DataVerify
5559
{
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace Gravity\Validations\String;
4+
5+
use Gravity\Validations\ValidationStrategy;
6+
use Gravity\Attributes\{ValidationRule, Param};
7+
8+
#[ValidationRule(
9+
name: 'disposableUrlDomain',
10+
description: 'Validates that a URL is not from a disposable/temporary domain (URL shorteners, free hosting, etc.)',
11+
category: 'String',
12+
examples: [
13+
'$verifier->field("website")->disposableUrlDomain',
14+
'$verifier->field("website")->disposableUrlDomain(["bit.ly", "tinyurl.com"])'
15+
]
16+
)]
17+
class DisposableUrlDomainValidation extends ValidationStrategy
18+
{
19+
/**
20+
* List of known disposable/temporary URL domain patterns
21+
*
22+
* Categories:
23+
* - URL Shorteners
24+
* - Temporary file hosting
25+
* - Free subdomain services
26+
* - Suspicious free hosting
27+
*
28+
* @var array<string>
29+
*/
30+
public const DEFAULTS = [
31+
'bit.ly','tinyurl.com','goo.gl','ow.ly','t.co','is.gd','buff.ly','adf.ly','bc.vc','soo.gd','clk.im','s2r.co','shrtco.de',
32+
'rb.gy','cutt.ly','short.io','tiny.cc','file.io','transfer.sh','temp.sh','tmpfiles.org','0x0.st','uguu.se','catbox.moe',
33+
'litterbox.catbox.moe','pixeldrain.com','gofile.io','anonfiles.com','bayfiles.com','000webhostapp.com','freehosting.com',
34+
'freehostia.com','x10hosting.com','byethost.com','5gbfree.com','freewha.com','ngrok.io','ngrok-free.app','localtunnel.me',
35+
'serveo.net','localhost.run',
36+
];
37+
38+
public function getName(): string
39+
{
40+
return 'disposableUrlDomain';
41+
}
42+
43+
protected function handler(
44+
mixed $value,
45+
#[Param('Array of disposable domain patterns', example: [])]
46+
array $disposables = []
47+
): bool {
48+
if (!is_string($value) || $value === '') {
49+
return false;
50+
}
51+
52+
$host = parse_url($value, PHP_URL_HOST);
53+
54+
if ($host === null || $host === false || $host === '') {
55+
return false;
56+
}
57+
58+
$list = !empty($disposables) ? $disposables : self::DEFAULTS;
59+
60+
$host = strtolower($host);
61+
62+
foreach ($list as $disposable) {
63+
$disposableDomain = strtolower(ltrim($disposable, '.'));
64+
65+
if ($host === $disposableDomain) {
66+
return false;
67+
}
68+
69+
if (str_ends_with($host, '.' . $disposableDomain)) {
70+
return false;
71+
}
72+
}
73+
74+
return true;
75+
}
76+
}

0 commit comments

Comments
 (0)