Skip to content

Commit aabf9e6

Browse files
committed
Se cambia a PHP 8.5. Parche en phpstan.neon por problema con CoversClass. Se corrigen opciones obsoletas en helpers. Se agrega helper para IP y su test.
1 parent 63df11b commit aabf9e6

11 files changed

Lines changed: 389 additions & 16 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
strategy:
1818
matrix:
1919
os: [ubuntu-latest]
20-
php-version: ['8.3', '8.4']
20+
php-version: ['8.5']
2121

2222
steps:
2323
- name: Check out repository

composer.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@
2525
}
2626
},
2727
"require": {
28-
"php": "^8.3"
28+
"php": "^8.5"
2929
},
3030
"require-dev": {
3131
"ext-xdebug": "*",
32-
"friendsofphp/php-cs-fixer": "^3.63",
32+
"friendsofphp/php-cs-fixer": "^3.94",
3333
"phpstan/phpstan": "^1.12",
34-
"phpunit/phpunit": "^11.4",
35-
"league/csv": "^9.21",
36-
"nesbot/carbon": "^3.8",
37-
"symfony/mime": "^7.2",
38-
"maennchen/zipstream-php": "^3.1"
34+
"phpunit/phpunit": "^11.5",
35+
"league/csv": "^9.28",
36+
"nesbot/carbon": "^3.11",
37+
"symfony/mime": "^7.4",
38+
"maennchen/zipstream-php": "^3.2"
3939
},
4040
"scripts": {
4141
"docs": "php tools/phpdocumentor run --config=phpdoc.xml",

php-cs-fixer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
declare(strict_types=1);
44

55
/**
6-
* Derafu: Foundation - Base for Derafu's Projects.
6+
* Derafu: Support - Essential PHP Utilities.
77
*
88
* Copyright (c) 2025 Esteban De La Fuente Rubio / Derafu <https://www.derafu.dev>
99
* Licensed under the MIT License.

phpstan.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ parameters:
55
paths:
66
- src
77
- tests
8+
# PHPUnit's CoversClass is repeatable (Attribute::IS_REPEATABLE); PHPStan does not read it.
9+
# Ignore only this message so real non-repeatable attribute errors are still reported.
10+
ignoreErrors:
11+
- message: '#CoversClass.*is not repeatable#'
12+
identifier: attribute.nonRepeatable

src/Csv.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public static function load(
5151
$content = mb_convert_encoding($content, 'UTF-8', $encoding);
5252
}
5353

54-
$csv = Reader::createFromString($content);
54+
$csv = Reader::fromString($content);
5555
$csv->setDelimiter($separator);
5656
$csv->setEnclosure($enclosure);
5757
$csv->setEscape($escape);
@@ -153,7 +153,7 @@ public static function generate(
153153
string $encoding = 'UTF-8'
154154
): string {
155155
try {
156-
$csv = Writer::createFromString('');
156+
$csv = Writer::fromString('');
157157
$csv->setDelimiter($separator);
158158
$csv->setEnclosure($enclosure);
159159
$csv->setEscape($escape);

src/Date.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,11 @@ public static function validateAndConvert(
255255
): ?string {
256256
try {
257257
$carbonDate = Carbon::createFromFormat('Y-m-d', $date);
258+
$lastErrors = $carbonDate->getLastErrors();
258259

259260
if (!$carbonDate instanceof Carbon ||
260261
$carbonDate->format('Y-m-d') !== $date ||
261-
$carbonDate->getLastErrors()['error_count'] > 0) {
262+
($lastErrors !== false && $lastErrors['error_count'] > 0)) {
262263
return null;
263264
}
264265

src/Hydrator.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ private static function tryAssignValue(
9292
// Try direct property assignment.
9393
if ($reflection->hasProperty($attribute)) {
9494
$property = $reflection->getProperty($attribute);
95-
$property->setAccessible(true);
9695
$property->setValue($instance, $value);
9796
return true;
9897
}

src/Ip.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+
/**
6+
* Derafu: Support - Essential PHP Utilities.
7+
*
8+
* Copyright (c) 2025 Esteban De La Fuente Rubio / Derafu <https://www.derafu.dev>
9+
* Licensed under the MIT License.
10+
* See LICENSE file for more details.
11+
*/
12+
13+
namespace Derafu\Support;
14+
15+
/**
16+
* IP utilities.
17+
*
18+
* Provides functionality to get the real client IP address from various headers
19+
* and validate if it is private or reserved.
20+
*/
21+
final class Ip
22+
{
23+
/**
24+
* Gets the real client IP address from various headers.
25+
*
26+
* @return string The real client IP address.
27+
*/
28+
public static function getRealClientIp(): string
29+
{
30+
// Headers to check in order of priority.
31+
$headers = [
32+
'HTTP_X_FORWARDED_FOR',
33+
'HTTP_X_REAL_IP',
34+
'HTTP_CF_CONNECTING_IP', // Cloudflare.
35+
'HTTP_X_CLIENT_IP',
36+
'HTTP_X_FORWARDED',
37+
'HTTP_FORWARDED_FOR',
38+
'HTTP_FORWARDED',
39+
'HTTP_X_CLUSTER_CLIENT_IP',
40+
'HTTP_X_FORWARDED_FOR_IP',
41+
'HTTP_VIA',
42+
'HTTP_X_VIA',
43+
'HTTP_X_COMING_FROM',
44+
'HTTP_COMING_FROM',
45+
'HTTP_X_COMING_FROM_IP',
46+
];
47+
48+
foreach ($headers as $header) {
49+
if (!empty($_SERVER[$header])) {
50+
$ip = $_SERVER[$header];
51+
52+
// If there are multiple IPs (separated by comma), take the first one.
53+
if (str_contains($ip, ',')) {
54+
$ips = explode(',', $ip);
55+
$ip = trim($ips[0]);
56+
}
57+
58+
// Validate that it is a valid IP address.
59+
$isValid = filter_var(
60+
$ip,
61+
FILTER_VALIDATE_IP,
62+
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
63+
);
64+
if ($isValid !== false) {
65+
return $ip;
66+
}
67+
}
68+
}
69+
70+
// Fallback to REMOTE_ADDR.
71+
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
72+
}
73+
74+
/**
75+
* Gets the real client IP address (alias for compatibility).
76+
*
77+
* @return string The real client IP address.
78+
*/
79+
public static function getClientIp(): string
80+
{
81+
return self::getRealClientIp();
82+
}
83+
84+
/**
85+
* Checks if the IP is private.
86+
*
87+
* @param string $ip The IP address to check.
88+
* @return bool True if the IP is private.
89+
*/
90+
public static function isPrivateIp(string $ip): bool
91+
{
92+
return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE);
93+
}
94+
95+
/**
96+
* Checks if the IP is reserved.
97+
*
98+
* @param string $ip The IP address to check.
99+
* @return bool True if the IP is reserved.
100+
*/
101+
public static function isReservedIp(string $ip): bool
102+
{
103+
return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE);
104+
}
105+
106+
/**
107+
* Gets detailed information about the client IP address.
108+
*
109+
* Formatted as:
110+
*
111+
* ```php
112+
* [
113+
* 'real_ip' => string,
114+
* 'remote_addr' => string,
115+
* 'is_private' => bool,
116+
* 'is_reserved' => bool,
117+
* 'headers' => array<string,string|null>,
118+
* ]
119+
* ```
120+
*
121+
* @return array<string,mixed> Detailed information about the client IP address.
122+
*/
123+
public static function getClientIpInfo(): array
124+
{
125+
$realIp = self::getRealClientIp();
126+
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
127+
128+
return [
129+
'real_ip' => $realIp,
130+
'remote_addr' => $remoteAddr,
131+
'is_private' => self::isPrivateIp($realIp),
132+
'is_reserved' => self::isReservedIp($realIp),
133+
'headers' => [
134+
'X-Forwarded-For' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null,
135+
'X-Real-IP' => $_SERVER['HTTP_X_REAL_IP'] ?? null,
136+
'CF-Connecting-IP' => $_SERVER['HTTP_CF_CONNECTING_IP'] ?? null,
137+
'X-Client-IP' => $_SERVER['HTTP_X_CLIENT_IP'] ?? null,
138+
],
139+
];
140+
}
141+
}

src/Str.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,8 @@ public static function utf8decode(string $string): string
352352

353353
$result = mb_convert_encoding($string, 'ISO-8859-1', 'UTF-8');
354354

355-
return $result !== false ? $result : $string;
355+
// $result always is a string. Maybe just return $result in the future.
356+
return $result ?: $string;
356357
}
357358

358359
/**
@@ -371,7 +372,8 @@ public static function utf8encode(string $string): string
371372

372373
$result = mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1');
373374

374-
return $result !== false ? $result : $string;
375+
// $result always is a string. Maybe just return $result in the future.
376+
return $result ?: $string;
375377
}
376378

377379
/**

0 commit comments

Comments
 (0)