diff --git a/CHANGELOG.md b/CHANGELOG.md index e47de40c..bb63a985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +7.3 +--- + + * Add casters for `Dba\Connection`, `SQLite3Result`, `OpenSSLAsymmetricKey` and `OpenSSLCertificateSigningRequest` + * Deprecate `ResourceCaster::castCurl()`, `ResourceCaster::castGd()` and `ResourceCaster::castOpensslX509()` + * Mark all casters as `@internal` + 7.2 --- diff --git a/Caster/AddressInfoCaster.php b/Caster/AddressInfoCaster.php new file mode 100644 index 00000000..f341c688 --- /dev/null +++ b/Caster/AddressInfoCaster.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @internal since Symfony 7.3 + */ +final class AddressInfoCaster +{ + private const MAPS = [ + 'ai_flags' => [ + 1 => 'AI_PASSIVE', + 2 => 'AI_CANONNAME', + 4 => 'AI_NUMERICHOST', + 8 => 'AI_V4MAPPED', + 16 => 'AI_ALL', + 32 => 'AI_ADDRCONFIG', + 64 => 'AI_IDN', + 128 => 'AI_CANONIDN', + 1024 => 'AI_NUMERICSERV', + ], + 'ai_family' => [ + 1 => 'AF_UNIX', + 2 => 'AF_INET', + 10 => 'AF_INET6', + 44 => 'AF_DIVERT', + ], + 'ai_socktype' => [ + 1 => 'SOCK_STREAM', + 2 => 'SOCK_DGRAM', + 3 => 'SOCK_RAW', + 4 => 'SOCK_RDM', + 5 => 'SOCK_SEQPACKET', + ], + 'ai_protocol' => [ + 1 => 'SOL_SOCKET', + 6 => 'SOL_TCP', + 17 => 'SOL_UDP', + 136 => 'SOL_UDPLITE', + ], + ]; + + public static function castAddressInfo(\AddressInfo $h, array $a, Stub $stub, bool $isNested): array + { + static $resolvedMaps; + + if (!$resolvedMaps) { + foreach (self::MAPS as $k => $map) { + foreach ($map as $v => $name) { + if (\defined($name)) { + $resolvedMaps[$k][\constant($name)] = $name; + } elseif (!isset($resolvedMaps[$k][$v])) { + $resolvedMaps[$k][$v] = $name; + } + } + } + } + + foreach (socket_addrinfo_explain($h) as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = match (true) { + 'ai_flags' === $k => ConstStub::fromBitfield($v, $resolvedMaps[$k]), + isset($resolvedMaps[$k][$v]) => new ConstStub($resolvedMaps[$k][$v], $v), + default => $v, + }; + } + + return $a; + } +} diff --git a/Caster/AmqpCaster.php b/Caster/AmqpCaster.php index 68b1a65f..ff56288b 100644 --- a/Caster/AmqpCaster.php +++ b/Caster/AmqpCaster.php @@ -19,6 +19,8 @@ * @author Grégoire Pineau * * @final + * + * @internal since Symfony 7.3 */ class AmqpCaster { diff --git a/Caster/Caster.php b/Caster/Caster.php index cc213ab5..c3bc54e3 100644 --- a/Caster/Caster.php +++ b/Caster/Caster.php @@ -46,6 +46,8 @@ class Caster * Casts objects to arrays and adds the dynamic property prefix. * * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not + * + * @internal since Symfony 7.3 */ public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, ?string $debugClass = null): array { @@ -162,6 +164,9 @@ public static function filter(array $a, int $filter, array $listedProperties = [ return $a; } + /** + * @internal since Symfony 7.3 + */ public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array { if (isset($a['__PHP_Incomplete_Class_Name'])) { diff --git a/Caster/ConstStub.php b/Caster/ConstStub.php index 587c6c39..adea7860 100644 --- a/Caster/ConstStub.php +++ b/Caster/ConstStub.php @@ -30,4 +30,23 @@ public function __toString(): string { return (string) $this->value; } + + /** + * @param array $values + */ + public static function fromBitfield(int $value, array $values): self + { + $names = []; + foreach ($values as $v => $name) { + if ($value & $v) { + $names[] = $name; + } + } + + if (!$names) { + $names[] = $values[0] ?? 0; + } + + return new self(implode(' | ', $names), $value); + } } diff --git a/Caster/CurlCaster.php b/Caster/CurlCaster.php new file mode 100644 index 00000000..fe4ec525 --- /dev/null +++ b/Caster/CurlCaster.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class CurlCaster +{ + public static function castCurl(\CurlHandle $h, array $a, Stub $stub, bool $isNested): array + { + foreach (curl_getinfo($h) as $key => $val) { + $a[Caster::PREFIX_VIRTUAL.$key] = $val; + } + + return $a; + } +} diff --git a/Caster/DOMCaster.php b/Caster/DOMCaster.php index fa58ec4c..e16b33d4 100644 --- a/Caster/DOMCaster.php +++ b/Caster/DOMCaster.php @@ -19,6 +19,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class DOMCaster { diff --git a/Caster/DateCaster.php b/Caster/DateCaster.php index f6c35f2b..453d0cb9 100644 --- a/Caster/DateCaster.php +++ b/Caster/DateCaster.php @@ -19,6 +19,8 @@ * @author Dany Maillard * * @final + * + * @internal since Symfony 7.3 */ class DateCaster { diff --git a/Caster/DoctrineCaster.php b/Caster/DoctrineCaster.php index 74c06a41..b963112f 100644 --- a/Caster/DoctrineCaster.php +++ b/Caster/DoctrineCaster.php @@ -22,6 +22,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class DoctrineCaster { diff --git a/Caster/ExceptionCaster.php b/Caster/ExceptionCaster.php index fb67a704..4473bdc8 100644 --- a/Caster/ExceptionCaster.php +++ b/Caster/ExceptionCaster.php @@ -22,6 +22,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class ExceptionCaster { diff --git a/Caster/GdCaster.php b/Caster/GdCaster.php new file mode 100644 index 00000000..db87653e --- /dev/null +++ b/Caster/GdCaster.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class GdCaster +{ + public static function castGd(\GdImage $gd, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'size'] = imagesx($gd).'x'.imagesy($gd); + $a[Caster::PREFIX_VIRTUAL.'trueColor'] = imageistruecolor($gd); + + return $a; + } +} diff --git a/Caster/GmpCaster.php b/Caster/GmpCaster.php index b018cc7f..325d2e90 100644 --- a/Caster/GmpCaster.php +++ b/Caster/GmpCaster.php @@ -20,6 +20,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class GmpCaster { diff --git a/Caster/ImagineCaster.php b/Caster/ImagineCaster.php index d1289da3..0fb2a903 100644 --- a/Caster/ImagineCaster.php +++ b/Caster/ImagineCaster.php @@ -16,6 +16,8 @@ /** * @author Grégoire Pineau + * + * @internal since Symfony 7.3 */ final class ImagineCaster { diff --git a/Caster/IntlCaster.php b/Caster/IntlCaster.php index f386c721..529c8f76 100644 --- a/Caster/IntlCaster.php +++ b/Caster/IntlCaster.php @@ -18,6 +18,8 @@ * @author Jan Schädlich * * @final + * + * @internal since Symfony 7.3 */ class IntlCaster { diff --git a/Caster/MemcachedCaster.php b/Caster/MemcachedCaster.php index 740785ce..4e4f611f 100644 --- a/Caster/MemcachedCaster.php +++ b/Caster/MemcachedCaster.php @@ -17,6 +17,8 @@ * @author Jan Schädlich * * @final + * + * @internal since Symfony 7.3 */ class MemcachedCaster { diff --git a/Caster/OpenSSLCaster.php b/Caster/OpenSSLCaster.php new file mode 100644 index 00000000..4c311ac4 --- /dev/null +++ b/Caster/OpenSSLCaster.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * @author Alexandre Daubois + * + * @internal + */ +final class OpenSSLCaster +{ + public static function castOpensslX509(\OpenSSLCertificate $h, array $a, Stub $stub, bool $isNested): array + { + $stub->cut = -1; + $info = openssl_x509_parse($h, false); + + $pin = openssl_pkey_get_public($h); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + $a += [ + Caster::PREFIX_VIRTUAL.'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])), + Caster::PREFIX_VIRTUAL.'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])), + Caster::PREFIX_VIRTUAL.'expiry' => new ConstStub(date(\DateTimeInterface::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), + Caster::PREFIX_VIRTUAL.'fingerprint' => new EnumStub([ + 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)), + 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)), + 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)), + 'pin-sha256' => new ConstStub($pin), + ]), + ]; + + return $a; + } + + public static function castOpensslAsymmetricKey(\OpenSSLAsymmetricKey $key, array $a, Stub $stub, bool $isNested): array + { + foreach (openssl_pkey_get_details($key) as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + unset($a[Caster::PREFIX_VIRTUAL.'rsa']); // binary data + + return $a; + } + + public static function castOpensslCsr(\OpenSSLCertificateSigningRequest $csr, array $a, Stub $stub, bool $isNested): array + { + foreach (openssl_csr_get_subject($csr, false) as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } +} diff --git a/Caster/PdoCaster.php b/Caster/PdoCaster.php index 1d364cdf..697e4122 100644 --- a/Caster/PdoCaster.php +++ b/Caster/PdoCaster.php @@ -19,6 +19,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class PdoCaster { diff --git a/Caster/PgSqlCaster.php b/Caster/PgSqlCaster.php index 3e759f69..54a19064 100644 --- a/Caster/PgSqlCaster.php +++ b/Caster/PgSqlCaster.php @@ -19,6 +19,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class PgSqlCaster { diff --git a/Caster/ProxyManagerCaster.php b/Caster/ProxyManagerCaster.php index 736a6e75..0d954f48 100644 --- a/Caster/ProxyManagerCaster.php +++ b/Caster/ProxyManagerCaster.php @@ -18,6 +18,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class ProxyManagerCaster { diff --git a/Caster/RdKafkaCaster.php b/Caster/RdKafkaCaster.php index 5445b2d4..bfadef2f 100644 --- a/Caster/RdKafkaCaster.php +++ b/Caster/RdKafkaCaster.php @@ -28,6 +28,8 @@ * Casts RdKafka related classes to array representation. * * @author Romain Neutron + * + * @internal since Symfony 7.3 */ class RdKafkaCaster { diff --git a/Caster/RedisCaster.php b/Caster/RedisCaster.php index 5224bc05..a1ed95de 100644 --- a/Caster/RedisCaster.php +++ b/Caster/RedisCaster.php @@ -20,6 +20,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class RedisCaster { diff --git a/Caster/ReflectionCaster.php b/Caster/ReflectionCaster.php index e7bd9a15..e7310f40 100644 --- a/Caster/ReflectionCaster.php +++ b/Caster/ReflectionCaster.php @@ -19,6 +19,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class ReflectionCaster { diff --git a/Caster/ResourceCaster.php b/Caster/ResourceCaster.php index f775f81c..5613c553 100644 --- a/Caster/ResourceCaster.php +++ b/Caster/ResourceCaster.php @@ -19,16 +19,31 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class ResourceCaster { + /** + * @deprecated since Symfony 7.3 + */ public static function castCurl(\CurlHandle $h, array $a, Stub $stub, bool $isNested): array { - return curl_getinfo($h); + trigger_deprecation('symfony/var-dumper', '7.3', 'The "%s()" method is deprecated without replacement.', __METHOD__, CurlCaster::class); + + return CurlCaster::castCurl($h, $a, $stub, $isNested); } - public static function castDba($dba, array $a, Stub $stub, bool $isNested): array + /** + * @param resource|\Dba\Connection $dba + */ + public static function castDba(mixed $dba, array $a, Stub $stub, bool $isNested): array { + if (\PHP_VERSION_ID < 80402 && !\is_resource($dba)) { + // @see https://github.com/php/php-src/issues/16990 + return $a; + } + $list = dba_list(); $a['file'] = $list[(int) $dba]; @@ -55,37 +70,23 @@ public static function castStreamContext($stream, array $a, Stub $stub, bool $is return @stream_context_get_params($stream) ?: $a; } - public static function castGd($gd, array $a, Stub $stub, bool $isNested): array + /** + * @deprecated since Symfony 7.3 + */ + public static function castGd(\GdImage $gd, array $a, Stub $stub, bool $isNested): array { - $a['size'] = imagesx($gd).'x'.imagesy($gd); - $a['trueColor'] = imageistruecolor($gd); + trigger_deprecation('symfony/var-dumper', '7.3', 'The "%s()" method is deprecated without replacement.', __METHOD__, GdCaster::class); - return $a; + return GdCaster::castGd($gd, $a, $stub, $isNested); } - public static function castOpensslX509($h, array $a, Stub $stub, bool $isNested): array + /** + * @deprecated since Symfony 7.3 + */ + public static function castOpensslX509(\OpenSSLCertificate $h, array $a, Stub $stub, bool $isNested): array { - $stub->cut = -1; - $info = openssl_x509_parse($h, false); - - $pin = openssl_pkey_get_public($h); - $pin = openssl_pkey_get_details($pin)['key']; - $pin = \array_slice(explode("\n", $pin), 1, -2); - $pin = base64_decode(implode('', $pin)); - $pin = base64_encode(hash('sha256', $pin, true)); - - $a += [ - 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])), - 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])), - 'expiry' => new ConstStub(date(\DateTimeInterface::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), - 'fingerprint' => new EnumStub([ - 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)), - 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)), - 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)), - 'pin-sha256' => new ConstStub($pin), - ]), - ]; + trigger_deprecation('symfony/var-dumper', '7.3', 'The "%s()" method is deprecated without replacement.', __METHOD__, OpenSSLCaster::class); - return $a; + return OpenSSLCaster::castOpensslX509($h, $a, $stub, $isNested); } } diff --git a/Caster/SocketCaster.php b/Caster/SocketCaster.php new file mode 100644 index 00000000..6b95cd10 --- /dev/null +++ b/Caster/SocketCaster.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * @author Alexandre Daubois + * + * @internal + */ +final class SocketCaster +{ + public static function castSocket(\Socket $socket, array $a, Stub $stub, bool $isNested): array + { + socket_getsockname($socket, $addr, $port); + $info = stream_get_meta_data(socket_export_stream($socket)); + + if (\PHP_VERSION_ID >= 80300) { + $uri = ($info['uri'] ?? '//'); + if (str_starts_with($uri, 'unix://')) { + $uri .= $addr; + } else { + $uri .= \sprintf(str_contains($addr, ':') ? '[%s]:%s' : '%s:%s', $addr, $port); + } + + $a[Caster::PREFIX_VIRTUAL.'uri'] = $uri; + + if (@socket_atmark($socket)) { + $a[Caster::PREFIX_VIRTUAL.'atmark'] = true; + } + } + + $a += [ + Caster::PREFIX_VIRTUAL.'timed_out' => $info['timed_out'], + Caster::PREFIX_VIRTUAL.'blocked' => $info['blocked'], + ]; + + if (!$lastError = socket_last_error($socket)) { + return $a; + } + + static $errors; + + if (!$errors) { + $errors = get_defined_constants(true)['sockets'] ?? []; + $errors = array_flip(array_filter($errors, static fn ($k) => str_starts_with($k, 'SOCKET_E'), \ARRAY_FILTER_USE_KEY)); + } + + $a[Caster::PREFIX_VIRTUAL.'last_error'] = new ConstStub($errors[$lastError], socket_strerror($lastError)); + + return $a; + } +} diff --git a/Caster/SplCaster.php b/Caster/SplCaster.php index bd2bcc04..31f4b11c 100644 --- a/Caster/SplCaster.php +++ b/Caster/SplCaster.php @@ -19,6 +19,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class SplCaster { diff --git a/Caster/SqliteCaster.php b/Caster/SqliteCaster.php new file mode 100644 index 00000000..25d47ac4 --- /dev/null +++ b/Caster/SqliteCaster.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Alexandre Daubois + * + * @internal + */ +final class SqliteCaster +{ + public static function castSqlite3Result(\SQLite3Result $result, array $a, Stub $stub, bool $isNested): array + { + $numColumns = $result->numColumns(); + for ($i = 0; $i < $numColumns; ++$i) { + $a[Caster::PREFIX_VIRTUAL.'columnNames'][$i] = $result->columnName($i); + } + + return $a; + } +} diff --git a/Caster/StubCaster.php b/Caster/StubCaster.php index 56742b01..85cf9973 100644 --- a/Caster/StubCaster.php +++ b/Caster/StubCaster.php @@ -19,6 +19,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class StubCaster { diff --git a/Caster/SymfonyCaster.php b/Caster/SymfonyCaster.php index d8422e05..42dc901a 100644 --- a/Caster/SymfonyCaster.php +++ b/Caster/SymfonyCaster.php @@ -19,6 +19,8 @@ /** * @final + * + * @internal since Symfony 7.3 */ class SymfonyCaster { diff --git a/Caster/UuidCaster.php b/Caster/UuidCaster.php index b1027745..732ad7cc 100644 --- a/Caster/UuidCaster.php +++ b/Caster/UuidCaster.php @@ -16,6 +16,8 @@ /** * @author Grégoire Pineau + * + * @internal since Symfony 7.3 */ final class UuidCaster { diff --git a/Caster/XmlReaderCaster.php b/Caster/XmlReaderCaster.php index 672fec68..00420c79 100644 --- a/Caster/XmlReaderCaster.php +++ b/Caster/XmlReaderCaster.php @@ -19,6 +19,8 @@ * @author Baptiste Clavié * * @final + * + * @internal since Symfony 7.3 */ class XmlReaderCaster { diff --git a/Caster/XmlResourceCaster.php b/Caster/XmlResourceCaster.php index fd3d3a2a..f6b08965 100644 --- a/Caster/XmlResourceCaster.php +++ b/Caster/XmlResourceCaster.php @@ -19,6 +19,8 @@ * @author Nicolas Grekas * * @final + * + * @internal since Symfony 7.3 */ class XmlResourceCaster { diff --git a/Cloner/AbstractCloner.php b/Cloner/AbstractCloner.php index 3cd46942..9038d2c0 100644 --- a/Cloner/AbstractCloner.php +++ b/Cloner/AbstractCloner.php @@ -24,6 +24,9 @@ abstract class AbstractCloner implements ClonerInterface public static array $defaultCasters = [ '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'], + 'AddressInfo' => ['Symfony\Component\VarDumper\Caster\AddressInfoCaster', 'castAddressInfo'], + 'Socket' => ['Symfony\Component\VarDumper\Caster\SocketCaster', 'castSocket'], + 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], @@ -174,29 +177,33 @@ abstract class AbstractCloner implements ClonerInterface 'mysqli_driver' => ['Symfony\Component\VarDumper\Caster\MysqliCaster', 'castMysqliDriver'], - 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\CurlCaster', 'castCurl'], + 'Dba\Connection' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], - ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], - ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], - ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], - ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], - ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], + 'SQLite3Result' => ['Symfony\Component\VarDumper\Caster\SqliteCaster', 'castSqlite3Result'], + + 'PgSql\Lob' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], + 'PgSql\Connection' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + 'PgSql\Result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], + ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], - 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], - ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + 'OpenSSLAsymmetricKey' => ['Symfony\Component\VarDumper\Caster\OpenSSLCaster', 'castOpensslAsymmetricKey'], + 'OpenSSLCertificateSigningRequest' => ['Symfony\Component\VarDumper\Caster\OpenSSLCaster', 'castOpensslCsr'], + 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\OpenSSLCaster', 'castOpensslX509'], ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], - ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + + 'Socket' => ['Symfony\Component\VarDumper\Caster\SocketCaster', 'castSocket'], 'RdKafka' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castRdKafka'], 'RdKafka\Conf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castConf'], diff --git a/Cloner/VarCloner.php b/Cloner/VarCloner.php index 170c8b40..6a7ec282 100644 --- a/Cloner/VarCloner.php +++ b/Cloner/VarCloner.php @@ -28,14 +28,12 @@ protected function doClone(mixed $var): array $objRefs = []; // Map of original object handles to their stub object counterpart $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning $resRefs = []; // Map of original resource handles to their stub object counterpart - $values = []; // Map of stub objects' ids to original values $maxItems = $this->maxItems; $maxString = $this->maxString; $minDepth = $this->minDepth; $currentDepth = 0; // Current tree depth $currentDepthFinalIndex = 0; // Final $queue index for current tree depth $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached - $cookie = (object) []; // Unique object used to detect hard references $a = null; // Array cast for nested structures $stub = null; // Stub capturing the main properties of an original item value // or null if the original value is used directly @@ -53,7 +51,7 @@ protected function doClone(mixed $var): array } } - $refs = $vals = $queue[$i]; + $vals = $queue[$i]; foreach ($vals as $k => $v) { // $v is the original value or a stub object in case of hard references @@ -215,10 +213,6 @@ protected function doClone(mixed $var): array $queue[$i] = $vals; } - foreach ($values as $h => $v) { - $hardRefs[$h] = $v; - } - return $queue; } } diff --git a/Dumper/HtmlDumper.php b/Dumper/HtmlDumper.php index 1f24852c..835d6d94 100644 --- a/Dumper/HtmlDumper.php +++ b/Dumper/HtmlDumper.php @@ -867,20 +867,20 @@ protected function style(string $style, string $value, array $attr = []): string } $label = esc(substr($value, -$attr['ellipsis'])); $dumpTitle = $v."\n".$dumpTitle; - $v = sprintf('%s', $ellipsisClass, substr($v, 0, -\strlen($label))); + $v = \sprintf('%s', $ellipsisClass, substr($v, 0, -\strlen($label))); if (!empty($attr['ellipsis-tail'])) { $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); - $v .= sprintf('%s%s', $ellipsisClass, substr($label, 0, $tail), substr($label, $tail)); + $v .= \sprintf('%s%s', $ellipsisClass, substr($label, 0, $tail), substr($label, $tail)); } else { - $v .= sprintf('%s', $label); + $v .= \sprintf('%s', $label); } } $map = static::$controlCharsMap; - $v = sprintf( + $v = \sprintf( '%s', - 1 === count($dumpClasses) ? '' : '"', + 1 === \count($dumpClasses) ? '' : '"', implode(' ', $dumpClasses), $dumpTitle ? ' title="'.$dumpTitle.'"' : '', preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { diff --git a/Resources/functions/dump.php b/Resources/functions/dump.php index e6ade0df..c9915514 100644 --- a/Resources/functions/dump.php +++ b/Resources/functions/dump.php @@ -45,7 +45,7 @@ function dump(mixed ...$vars): mixed if (!function_exists('dd')) { function dd(mixed ...$vars): never { - if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) && !headers_sent()) { + if (!in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) && !headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); } diff --git a/Tests/Caster/AddressInfoCasterTest.php b/Tests/Caster/AddressInfoCasterTest.php new file mode 100644 index 00000000..1a95ab7e --- /dev/null +++ b/Tests/Caster/AddressInfoCasterTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Caster; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +/** + * @requires extension sockets + */ +class AddressInfoCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testCaster() + { + $xDump = <<assertDumpMatchesFormat($xDump, socket_addrinfo_lookup('localhost')[0]); + } +} diff --git a/Tests/Caster/CurlCasterTest.php b/Tests/Caster/CurlCasterTest.php new file mode 100644 index 00000000..a2d9bd10 --- /dev/null +++ b/Tests/Caster/CurlCasterTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Caster; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +/** + * @requires extension curl + */ +class CurlCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testCastCurl() + { + $ch = curl_init('http://example.com'); + curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true); + curl_exec($ch); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +CurlHandle { + url: "http://example.com/" + content_type: "text/html" + http_code: %d +%A +} +EODUMP, $ch); + } +} diff --git a/Tests/Caster/OpenSSLCasterTest.php b/Tests/Caster/OpenSSLCasterTest.php new file mode 100644 index 00000000..188228b8 --- /dev/null +++ b/Tests/Caster/OpenSSLCasterTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Caster; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +/** + * @requires extension openssl + */ +class OpenSSLCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testAsymmetricKey() + { + $key = openssl_pkey_new([ + 'private_key_bits' => 1024, + 'private_key_type' => \OPENSSL_KEYTYPE_RSA, + ]); + + if (false === $key) { + $this->markTestSkipped('Unable to generate a key pair'); + } + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +OpenSSLAsymmetricKey { + bits: 1024 + key: """ + -----BEGIN PUBLIC KEY-----\n + %A + %A + %A + %A + -----END PUBLIC KEY-----\n + """ + type: 0 +} +EODUMP, $key); + } + + public function testOpensslCsr() + { + $dn = [ + 'countryName' => 'FR', + 'stateOrProvinceName' => 'Ile-de-France', + 'localityName' => 'Paris', + 'organizationName' => 'Symfony', + 'organizationalUnitName' => 'Security', + 'commonName' => 'symfony.com', + 'emailAddress' => 'test@symfony.com', + ]; + $privkey = openssl_pkey_new(); + $csr = openssl_csr_new($dn, $privkey); + + if (false === $csr) { + $this->markTestSkipped('Unable to generate a CSR'); + } + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +OpenSSLCertificateSigningRequest { + countryName: "FR" + stateOrProvinceName: "Ile-de-France" + localityName: "Paris" + organizationName: "Symfony" + organizationalUnitName: "Security" + commonName: "symfony.com" + emailAddress: "test@symfony.com" +} +EODUMP, $csr); + } +} diff --git a/Tests/Caster/RdKafkaCasterTest.php b/Tests/Caster/RdKafkaCasterTest.php index 78b78ddc..592c3d64 100644 --- a/Tests/Caster/RdKafkaCasterTest.php +++ b/Tests/Caster/RdKafkaCasterTest.php @@ -61,6 +61,7 @@ public function testDumpConf() client.id: "rdkafka" %A dr_msg_cb: "0x%x" +%A } EODUMP; @@ -114,7 +115,7 @@ public function testDumpTopicConf() $expectedDump = <<assertDumpMatchesFormat( <<<'EOTXT' diff --git a/Tests/Caster/ResourceCasterTest.php b/Tests/Caster/ResourceCasterTest.php new file mode 100644 index 00000000..029f7fb0 --- /dev/null +++ b/Tests/Caster/ResourceCasterTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Caster; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; +use Symfony\Component\VarDumper\Caster\ResourceCaster; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +class ResourceCasterTest extends TestCase +{ + use ExpectUserDeprecationMessageTrait; + use VarDumperTestTrait; + + /** + * @group legacy + * + * @requires extension curl + */ + public function testCastCurlIsDeprecated() + { + $ch = curl_init('http://example.com'); + curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true); + curl_exec($ch); + + $this->expectUserDeprecationMessage('Since symfony/var-dumper 7.3: The "Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl()" method is deprecated without replacement.'); + + ResourceCaster::castCurl($ch, [], new Stub(), false); + } + + /** + * @group legacy + * + * @requires extension gd + */ + public function testCastGdIsDeprecated() + { + $gd = imagecreate(1, 1); + + $this->expectUserDeprecationMessage('Since symfony/var-dumper 7.3: The "Symfony\Component\VarDumper\Caster\ResourceCaster::castGd()" method is deprecated without replacement.'); + + ResourceCaster::castGd($gd, [], new Stub(), false); + } + + /** + * @requires PHP < 8.4 + * @requires extension dba + */ + public function testCastDbaPriorToPhp84() + { + $dba = dba_open(sys_get_temp_dir().'/test.db', 'c'); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +dba resource { + file: %s +} +EODUMP, $dba); + } + + /** + * @requires PHP 8.4 + */ + public function testCastDba() + { + if (\PHP_VERSION_ID < 80402) { + $this->markTestSkipped('The test cannot be run on PHP 8.4.0 and PHP 8.4.1, see https://github.com/php/php-src/issues/16990'); + } + + $dba = dba_open(sys_get_temp_dir().'/test.db', 'c'); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +Dba\Connection { + +file: %s +} +EODUMP, $dba); + } + + /** + * @requires PHP 8.4 + */ + public function testCastDbaOnBuggyPhp84() + { + if (\PHP_VERSION_ID >= 80402) { + $this->markTestSkipped('The test can only be run on PHP 8.4.0 and 8.4.1, see https://github.com/php/php-src/issues/16990'); + } + + $dba = dba_open(sys_get_temp_dir().'/test.db', 'c'); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +Dba\Connection { +} +EODUMP, $dba); + } +} diff --git a/Tests/Caster/SocketCasterTest.php b/Tests/Caster/SocketCasterTest.php new file mode 100644 index 00000000..741a9ddd --- /dev/null +++ b/Tests/Caster/SocketCasterTest.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Caster; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +/** + * @requires extension sockets + */ +class SocketCasterTest extends TestCase +{ + use VarDumperTestTrait; + + /** + * @requires PHP 8.3 + */ + public function testCastSocket() + { + $socket = socket_create(\AF_INET, \SOCK_DGRAM, \SOL_UDP); + @socket_connect($socket, '127.0.0.1', 80); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +Socket { + uri: "udp://127.0.0.1:%d" + timed_out: false + blocked: true%A +} +EODUMP, $socket); + } + + /** + * @requires PHP < 8.3 + */ + public function testCastSocketPriorToPhp83() + { + $socket = socket_create(\AF_INET, \SOCK_DGRAM, \SOL_UDP); + @socket_connect($socket, '127.0.0.1', 80); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +Socket { + timed_out: false + blocked: true +} +EODUMP, $socket); + } + + /** + * @requires PHP 8.3 + */ + public function testCastSocketIpV6() + { + $socket = socket_create(\AF_INET6, \SOCK_STREAM, \SOL_TCP); + @socket_connect($socket, '::1', 80); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +Socket { + uri: "tcp://[%A]:%d" + timed_out: false + blocked: true + last_error: SOCKET_ECONNREFUSED +} +EODUMP, $socket); + } + + /** + * @requires PHP < 8.3 + */ + public function testCastSocketIpV6PriorToPhp83() + { + $socket = socket_create(\AF_INET6, \SOCK_STREAM, \SOL_TCP); + @socket_connect($socket, '::1', 80); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +Socket { + timed_out: false + blocked: true + last_error: SOCKET_ECONNREFUSED +} +EODUMP, $socket); + } + + /** + * @requires PHP 8.3 + */ + public function testCastUnixSocket() + { + $socket = socket_create(\AF_UNIX, \SOCK_STREAM, 0); + @socket_connect($socket, '/tmp/socket.sock'); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +Socket { + uri: "unix://" + timed_out: false + blocked: true + last_error: SOCKET_ENOENT +} +EODUMP, $socket); + } + + /** + * @requires PHP < 8.3 + */ + public function testCastUnixSocketPriorToPhp83() + { + $socket = socket_create(\AF_UNIX, \SOCK_STREAM, 0); + @socket_connect($socket, '/tmp/socket.sock'); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +Socket { + timed_out: false + blocked: true + last_error: SOCKET_ENOENT +} +EODUMP, $socket); + } +} diff --git a/Tests/Caster/SqliteCasterTest.php b/Tests/Caster/SqliteCasterTest.php new file mode 100644 index 00000000..d616d2f0 --- /dev/null +++ b/Tests/Caster/SqliteCasterTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Caster; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +/** + * @requires extension sqlite3 + */ +class SqliteCasterTest extends TestCase +{ + use VarDumperTestTrait; + + public function testSqlite3Result() + { + $db = new \SQLite3(':memory:'); + $db->exec('CREATE TABLE foo (id INTEGER PRIMARY KEY, bar TEXT)'); + $db->exec('INSERT INTO foo (bar) VALUES ("baz")'); + $stmt = $db->prepare('SELECT id, bar FROM foo'); + $result = $stmt->execute(); + + $this->assertDumpMatchesFormat( + <<<'EODUMP' +SQLite3Result { + columnNames: array:2 [ + 0 => "id" + 1 => "bar" + ] +} +EODUMP, $result); + } +} diff --git a/Tests/Command/Descriptor/CliDescriptorTest.php b/Tests/Command/Descriptor/CliDescriptorTest.php index 5a24f1c1..f33e13be 100644 --- a/Tests/Command/Descriptor/CliDescriptorTest.php +++ b/Tests/Command/Descriptor/CliDescriptorTest.php @@ -70,7 +70,7 @@ public static function provideContext() source CliDescriptorTest.php on line 30 file /Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php -------- --------------------------------------------------------------------------------------------------- -TXT +TXT, ]; yield 'source full' => [ @@ -93,7 +93,7 @@ public static function provideContext() file src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php -------- -------------------------------------------------------------------------------- -TXT +TXT, ]; yield 'source with hyperlink' => [ @@ -127,7 +127,7 @@ public static function provideContext() ------ --------------------------------- date Fri, 14 Dec 2018 16:17:48 +0000 ------ --------------------------------- -TXT +TXT, ]; yield 'request' => [ @@ -147,7 +147,7 @@ public static function provideContext() date Fri, 14 Dec 2018 16:17:48 +0000 controller "FooController.php" ------------ --------------------------------- -TXT +TXT, ]; } } diff --git a/Tests/Command/Descriptor/HtmlDescriptorTest.php b/Tests/Command/Descriptor/HtmlDescriptorTest.php index bdf6a86c..0db5b3f6 100644 --- a/Tests/Command/Descriptor/HtmlDescriptorTest.php +++ b/Tests/Command/Descriptor/HtmlDescriptorTest.php @@ -91,7 +91,7 @@ public static function provideContext() [DUMPED] -TXT +TXT, ]; yield 'source full' => [ @@ -127,7 +127,7 @@ public static function provideContext() [DUMPED] -TXT +TXT, ]; yield 'cli' => [ @@ -155,7 +155,7 @@ public static function provideContext() [DUMPED] -TXT +TXT, ]; yield 'request' => [ @@ -189,7 +189,7 @@ public static function provideContext() [DUMPED] -TXT +TXT, ]; } } diff --git a/Tests/Dumper/CliDumperTest.php b/Tests/Dumper/CliDumperTest.php index ddacb0d7..14b53808 100644 --- a/Tests/Dumper/CliDumperTest.php +++ b/Tests/Dumper/CliDumperTest.php @@ -433,7 +433,7 @@ public static function provideDumpArrayWithColor() \e[0;38;5;208m"\e[38;5;113mfoo\e[0;38;5;208m" => "\e[1;38;5;113mbar\e[0;38;5;208m"\e[m \e[0;38;5;208m]\e[m -EOTXT +EOTXT, ]; yield [[], AbstractDumper::DUMP_LIGHT_ARRAY, "\e[0;38;5;208m[]\e[m\n"]; @@ -446,7 +446,7 @@ public static function provideDumpArrayWithColor() \e[0;38;5;208m"\e[38;5;113mfoo\e[0;38;5;208m" => "\e[1;38;5;113mbar\e[0;38;5;208m"\e[m \e[0;38;5;208m]\e[m -EOTXT +EOTXT, ]; yield [[], 0, "\e[0;38;5;208m[]\e[m\n"]; diff --git a/composer.json b/composer.json index eaba033e..ed312c52 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": {