Skip to content

Commit b70f8c3

Browse files
Add examples for range's VO and DT usage
1 parent 75bd932 commit b70f8c3

File tree

3 files changed

+383
-0
lines changed

3 files changed

+383
-0
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Enhances Doctrine with PostgreSQL-specific features and functions. Supports Post
1313
// Register types with Doctrine
1414
Type::addType('jsonb', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\Jsonb");
1515
Type::addType('text[]', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\TextArray");
16+
Type::addType('numrange', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\NumRange");
1617

1718
// Use in your Doctrine entities
1819
#[ORM\Column(type: 'jsonb')]
@@ -21,6 +22,9 @@ private array $data;
2122
#[ORM\Column(type: 'text[]')]
2223
private array $tags;
2324

25+
#[ORM\Column(type: 'numrange')]
26+
private NumericRange $priceRange;
27+
2428
// Use in DQL
2529
$query = $em->createQuery('
2630
SELECT e
@@ -51,6 +55,9 @@ This package provides comprehensive Doctrine support for PostgreSQL features:
5155
- MAC addresses (`macaddr`, `macaddr[]`)
5256
- **Geometric Types**
5357
- Point (`point`, `point[]`)
58+
- **Range Types**
59+
- Date and time ranges (`daterange`, `tsrange`, `tstzrange`)
60+
- Numeric ranges (`numrange`, `int4range`, `int8range`)
5461

5562
### PostgreSQL Operators
5663
- **Array Operations**
@@ -62,6 +69,9 @@ This package provides comprehensive Doctrine support for PostgreSQL features:
6269
- Field access (`->`, `->>`)
6370
- Path operations (`#>`, `#>>`)
6471
- JSON containment and existence operators
72+
- **Range Operations**
73+
- Containment checks (in PHP value objects and for DQL queries with `@>`)
74+
- Overlaps (`&&`)
6575

6676
### Functions
6777
- **Text Search**
@@ -85,6 +95,7 @@ This package provides comprehensive Doctrine support for PostgreSQL features:
8595

8696
Full documentation:
8797
- [Available Types](docs/AVAILABLE-TYPES.md)
98+
- [Value Objects for Range Types](docs/RANGE-TYPES.md)
8899
- [Available Functions and Operators](docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md)
89100
- [Common Use Cases and Examples](docs/USE-CASES-AND-EXAMPLES.md)
90101

docs/RANGE-TYPES.md

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
# PostgreSQL Range Types
2+
3+
PostgreSQL range types represent ranges of values of some element type (called the range's subtype). This library provides support for all PostgreSQL built-in range types.
4+
5+
## Available Range Types
6+
7+
| Range Type | PostgreSQL Type | Value Type | Description |
8+
|---|---|---|---|
9+
| DateRange | DATERANGE | DateTimeInterface | Date ranges (without time) |
10+
| Int4Range | INT4RANGE | int | 4-byte integer ranges |
11+
| Int8Range | INT8RANGE | int | 8-byte integer ranges |
12+
| NumRange | NUMRANGE | int/float | Numeric ranges with arbitrary precision |
13+
| TsRange | TSRANGE | DateTimeInterface | Timestamp ranges without timezone |
14+
| TstzRange | TSTZRANGE | DateTimeInterface | Timestamp ranges with timezone |
15+
16+
## Basic Usage
17+
18+
### Registration
19+
20+
First, register the range types you need:
21+
22+
```php
23+
use Doctrine\DBAL\Types\Type;
24+
25+
Type::addType('daterange', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\DateRange");
26+
Type::addType('int4range', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\Int4Range");
27+
Type::addType('int8range', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\Int8Range");
28+
Type::addType('numrange', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\NumRange");
29+
Type::addType('tsrange', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\TsRange");
30+
Type::addType('tstzrange', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\TstzRange");
31+
```
32+
33+
### Entity Usage
34+
35+
```php
36+
use Doctrine\ORM\Mapping as ORM;
37+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\DateRange;
38+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\NumericRange;
39+
40+
#[ORM\Entity]
41+
class Product
42+
{
43+
#[ORM\Column(type: 'numrange')]
44+
private ?NumericRange $priceRange = null;
45+
46+
#[ORM\Column(type: 'daterange')]
47+
private ?DateRange $availabilityPeriod = null;
48+
49+
public function setPriceRange(float $min, float $max): void
50+
{
51+
$this->priceRange = new NumericRange($min, $max);
52+
}
53+
54+
public function setAvailabilityPeriod(\DateTimeInterface $start, \DateTimeInterface $end): void
55+
{
56+
$this->availabilityPeriod = new DateRange($start, $end);
57+
}
58+
}
59+
```
60+
61+
## Range Construction
62+
63+
### Inclusive vs Exclusive Bounds
64+
65+
Ranges support both inclusive `[` and exclusive `(` bounds:
66+
67+
```php
68+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\NumericRange;
69+
70+
// [1.0, 10.0) - includes 1.0, excludes 10.0
71+
$range = new NumericRange(1.0, 10.0, true, false);
72+
73+
// (0, 100] - excludes 0, includes 100
74+
$range = new NumericRange(0, 100, false, true);
75+
76+
// [5, 15] - includes both bounds
77+
$range = new NumericRange(5, 15, true, true);
78+
```
79+
80+
### Infinite Ranges
81+
82+
Ranges can be unbounded on either side:
83+
84+
```php
85+
// [10, ∞) - from 10 to infinity
86+
$range = new NumericRange(10, null, true, false);
87+
88+
// (-∞, 100] - from negative infinity to 100
89+
$range = new NumericRange(null, 100, false, true);
90+
91+
// (-∞, ∞) - infinite range
92+
$range = NumericRange::infinite();
93+
```
94+
95+
### Empty Ranges
96+
97+
```php
98+
// Create an explicitly empty range
99+
$range = NumericRange::empty();
100+
101+
// Check if a range is empty
102+
if ($range->isEmpty()) {
103+
// Handle empty range
104+
}
105+
```
106+
107+
## Numeric Ranges (NUMRANGE)
108+
109+
For arbitrary precision numeric values:
110+
111+
```php
112+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\NumericRange;
113+
114+
// Price range from €10.50 to €99.99
115+
$priceRange = new NumericRange(10.50, 99.99);
116+
117+
// Check if a price is in range
118+
if ($priceRange->contains(25.00)) {
119+
echo "Price is in range";
120+
}
121+
122+
// Create from PostgreSQL string
123+
$range = NumericRange::fromString('[10.5,99.99)');
124+
```
125+
126+
## Integer Ranges
127+
128+
### Int4Range (4-byte integers)
129+
130+
```php
131+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\Int4Range;
132+
133+
// Age range
134+
$ageRange = new Int4Range(18, 65);
135+
136+
// Check if age is valid
137+
if ($ageRange->contains(25)) {
138+
echo "Age is valid";
139+
}
140+
```
141+
142+
### Int8Range (8-byte integers)
143+
144+
```php
145+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\Int8Range;
146+
147+
// Large number range
148+
$range = new Int8Range(PHP_INT_MIN, PHP_INT_MAX);
149+
```
150+
151+
## Date Ranges (DATERANGE)
152+
153+
For date-only ranges without time components:
154+
155+
```php
156+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\DateRange;
157+
158+
// Event period
159+
$eventPeriod = new DateRange(
160+
new \DateTimeImmutable('2024-01-01'),
161+
new \DateTimeImmutable('2024-12-31')
162+
);
163+
164+
// Convenience methods
165+
$singleDay = DateRange::singleDay(new \DateTimeImmutable('2024-06-15'));
166+
$year2024 = DateRange::year(2024);
167+
$june2024 = DateRange::month(2024, 6);
168+
169+
// Check if a date falls within the range
170+
$checkDate = new \DateTimeImmutable('2024-06-15');
171+
if ($eventPeriod->contains($checkDate)) {
172+
echo "Date is within event period";
173+
}
174+
```
175+
176+
## Timestamp Ranges
177+
178+
### TsRange (without timezone)
179+
180+
```php
181+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\TsRange;
182+
183+
// Working hours
184+
$workingHours = new TsRange(
185+
new \DateTimeImmutable('2024-01-01 09:00:00'),
186+
new \DateTimeImmutable('2024-01-01 17:00:00')
187+
);
188+
```
189+
190+
### TstzRange (with timezone)
191+
192+
```php
193+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\TstzRange;
194+
195+
// Meeting time across UTC timezone
196+
$meetingTime = new TstzRange(
197+
new \DateTimeImmutable('2024-01-01 14:00:00+00:00'),
198+
new \DateTimeImmutable('2024-01-01 15:00:00+00:00')
199+
);
200+
```
201+
202+
## Range Operations
203+
204+
### Contains Check
205+
206+
```php
207+
$range = new NumericRange(1, 10);
208+
209+
if ($range->contains(5)) {
210+
echo "5 is in the range [1, 10)";
211+
}
212+
```
213+
214+
### String Representation
215+
216+
```php
217+
$range = new NumericRange(1.5, 10.7);
218+
echo $range; // Outputs: [1.5,10.7)
219+
220+
$range = new DateRange(
221+
new \DateTimeImmutable('2024-01-01'),
222+
new \DateTimeImmutable('2024-12-31')
223+
);
224+
echo $range; // Outputs: [2024-01-01,2024-12-31)
225+
```
226+
227+
### Parsing from String Values
228+
229+
```php
230+
// Parse PostgreSQL range strings
231+
$numRange = NumericRange::fromString('[1.5,10.7)');
232+
$dateRange = DateRange::fromString('[2024-01-01,2024-12-31)');
233+
$emptyRange = NumericRange::fromString('empty');
234+
```
235+
236+
## DQL Usage with Range Functions
237+
238+
Register range functions for DQL queries:
239+
240+
```php
241+
$configuration->addCustomStringFunction('DATERANGE', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Daterange::class);
242+
$configuration->addCustomStringFunction('INT4RANGE', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Int4range::class);
243+
$configuration->addCustomStringFunction('INT8RANGE', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Int8range::class);
244+
$configuration->addCustomStringFunction('NUMRANGE', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Numrange::class);
245+
$configuration->addCustomStringFunction('TSRANGE', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tsrange::class);
246+
$configuration->addCustomStringFunction('TSTZRANGE', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tstzrange::class);
247+
```
248+
249+
Use in DQL:
250+
251+
```php
252+
// Find products with overlapping price ranges
253+
$dql = "
254+
SELECT p
255+
FROM Product p
256+
WHERE OVERLAPS(p.priceRange, NUMRANGE(20, 50)) = TRUE
257+
";
258+
259+
// Find events in a date range
260+
$dql = "
261+
SELECT e
262+
FROM Event e
263+
WHERE CONTAINS(e.period, DATERANGE('2024-06-01', '2024-06-30')) = TRUE
264+
";
265+
```
266+
267+
## Common Use Cases
268+
269+
### Price Ranges
270+
271+
```php
272+
#[ORM\Entity]
273+
class Product
274+
{
275+
#[ORM\Column(type: 'numrange')]
276+
private ?NumericRange $priceRange = null;
277+
278+
public function setPriceRange(float $min, float $max): void
279+
{
280+
$this->priceRange = new NumericRange($min, $max, true, false);
281+
}
282+
283+
public function isInPriceRange(float $price): bool
284+
{
285+
return $this->priceRange?->contains($price) ?? false;
286+
}
287+
}
288+
```
289+
290+
### Availability Periods
291+
292+
```php
293+
#[ORM\Entity]
294+
class Room
295+
{
296+
#[ORM\Column(type: 'tstzrange')]
297+
private ?TstzRange $availabilityWindow = null;
298+
299+
public function setAvailability(\DateTimeInterface $start, \DateTimeInterface $end): void
300+
{
301+
$this->availabilityWindow = new TstzRange($start, $end);
302+
}
303+
304+
public function isAvailableAt(\DateTimeInterface $time): bool
305+
{
306+
return $this->availabilityWindow?->contains($time) ?? false;
307+
}
308+
}
309+
```
310+
311+
### Age Restrictions
312+
313+
```php
314+
#[ORM\Entity]
315+
class Event
316+
{
317+
#[ORM\Column(type: 'int4range')]
318+
private ?Int4Range $ageRestriction = null;
319+
320+
public function setAgeRestriction(int $minAge, int $maxAge): void
321+
{
322+
$this->ageRestriction = new Int4Range($minAge, $maxAge, true, true);
323+
}
324+
325+
public function isAgeAllowed(int $age): bool
326+
{
327+
return $this->ageRestriction?->contains($age) ?? true;
328+
}
329+
}
330+
```

0 commit comments

Comments
 (0)