first commit

This commit is contained in:
Claudecio Martins
2026-06-16 10:04:10 -03:00
commit a951944997
4463 changed files with 419677 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Henrique Moody
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+45
View File
@@ -0,0 +1,45 @@
# Respect\Stringifier
[![Build Status](https://img.shields.io/github/actions/workflow/status/Respect/Stringifier/continuous-integration.yml?branch=master&style=flat-square)](https://github.com/Respect/Stringifier/actions/workflows/continuous-integration.yml)
[![Code Coverage](https://img.shields.io/codecov/c/github/Respect/Stringifier?style=flat-square)](https://codecov.io/gh/Respect/Stringifier)
[![Latest Stable Version](https://img.shields.io/packagist/v/respect/stringifier.svg?style=flat-square)](https://packagist.org/packages/respect/stringifier)
[![Total Downloads](https://img.shields.io/packagist/dt/respect/stringifier.svg?style=flat-square)](https://packagist.org/packages/respect/stringifier)
[![License](https://img.shields.io/packagist/l/respect/stringifier.svg?style=flat-square)](https://packagist.org/packages/respect/stringifier)
Converts any PHP value into a string.
## Installation
Package is available on [Packagist](https://packagist.org/packages/respect/stringifier), you can install it
using [Composer](http://getcomposer.org).
```bash
composer require respect/stringifier
```
This library requires PHP >= 8.1.
## Feature Guide
Below a quick guide of how to use the library.
### Namespace import
Respect\Stringifier is namespaced, and you can make your life easier by importing
a single function into your context:
```php
use function Respect\Stringifier\stringify;
```
Stringifier was built using objects, the `stringify()` is a easy way to use it.
### Usage
Simply use the function to convert any value you want to:
```php
echo stringify($value);
```
To see more examples of how to use the library check the [integration tests](tests/integration).
+56
View File
@@ -0,0 +1,56 @@
{
"name": "respect/stringifier",
"description": "Converts any value to a string",
"keywords": ["respect", "stringifier", "stringify"],
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Respect/Stringifier Contributors",
"homepage": "https://github.com/Respect/Stringifier/graphs/contributors"
}
],
"require": {
"php": "^8.1"
},
"require-dev": {
"malukenho/docheader": "^0.1.7",
"phpstan/phpstan": "^1.10",
"phpstan/phpstan-deprecation-rules": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpstan/phpstan-strict-rules": "^1.5",
"phpunit/phpunit": "^10.0",
"respect/coding-standard": "^4.0",
"squizlabs/php_codesniffer": "^3.7"
},
"autoload": {
"psr-4": {
"Respect\\Stringifier\\": "src/",
"Respect\\Stringifier\\Test\\": "tests/src/",
"Respect\\Stringifier\\Test\\Unit\\": "tests/unit"
},
"files": [
"src/stringify.php"
]
},
"scripts": {
"docheader": "vendor/bin/docheader check src/ tests/",
"phpcs": "vendor/bin/phpcs",
"phpstan": "vendor/bin/phpstan",
"phpunit": "vendor/bin/phpunit",
"phpunit-integration": "vendor/bin/phpunit --testsuite=integration",
"phpunit-unit": "vendor/bin/phpunit --testsuite=unit",
"qa": [
"@docheader",
"@phpcs",
"@phpstan",
"@phpunit"
]
},
"config": {
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
+8
View File
@@ -0,0 +1,8 @@
parameters:
level: max
paths:
- src/
- tests/
fileExtensions:
- php
- phpt
+16
View File
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier;
interface Quoter
{
public function quote(string $string, int $depth): string;
}
+27
View File
@@ -0,0 +1,27 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Quoters;
use Respect\Stringifier\Quoter;
use function sprintf;
final class CodeQuoter implements Quoter
{
public function quote(string $string, int $depth): string
{
if ($depth === 0) {
return sprintf('`%s`', $string);
}
return $string;
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier;
interface Stringifier
{
public function stringify(mixed $raw, int $depth): ?string;
}
@@ -0,0 +1,73 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use function array_keys;
use function count;
use function implode;
use function is_array;
use function range;
use function sprintf;
final class ArrayStringifier implements Stringifier
{
public function __construct(
private readonly Stringifier $stringifier,
private readonly Quoter $quoter,
private readonly int $maximumDepth,
private readonly int $itemsLimit
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!is_array($raw)) {
return null;
}
if (empty($raw)) {
return $this->quoter->quote('{ }', $depth);
}
if ($depth >= $this->maximumDepth) {
return '...';
}
$items = [];
$itemsCount = 0;
$isSequential = $this->isSequential($raw);
foreach ($raw as $key => $value) {
if (++$itemsCount > $this->itemsLimit) {
$items[$itemsCount] = '...';
break;
}
$items[$itemsCount] = '';
if ($isSequential === false) {
$items[$itemsCount] .= sprintf('%s: ', $this->stringifier->stringify($key, $depth + 1));
}
$items[$itemsCount] .= $this->stringifier->stringify($value, $depth + 1);
}
return $this->quoter->quote(sprintf('{ %s }', implode(', ', $items)), $depth);
}
/**
* @param mixed[] $array
*/
private function isSequential(array $array): bool
{
return array_keys($array) === range(0, count($array) - 1);
}
}
@@ -0,0 +1,33 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use function is_bool;
final class BoolStringifier implements Stringifier
{
public function __construct(
private readonly Quoter $quoter
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!is_bool($raw)) {
return null;
}
return $this->quoter->quote($raw ? 'TRUE' : 'FALSE', $depth);
}
}
@@ -0,0 +1,85 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoters\CodeQuoter;
use Respect\Stringifier\Stringifier;
final class ClusterStringifier implements Stringifier
{
/**
* @var Stringifier[]
*/
private array $stringifiers = [];
/**
* @param Stringifier[] ...$stringifiers
*/
public function __construct(Stringifier ...$stringifiers)
{
$this->setStringifiers($stringifiers);
}
public static function createDefault(): self
{
$quoter = new CodeQuoter();
$stringifier = new self();
$stringifier->setStringifiers([
new TraversableStringifier($stringifier, $quoter),
new DateTimeStringifier($stringifier, $quoter, 'c'),
new ThrowableStringifier($stringifier, $quoter),
new StringableObjectStringifier($stringifier),
new JsonSerializableStringifier($stringifier, $quoter),
new ObjectStringifier($stringifier, $quoter),
new ArrayStringifier($stringifier, $quoter, 3, 5),
new InfiniteStringifier($quoter),
new NanStringifier($quoter),
new ResourceStringifier($quoter),
new BoolStringifier($quoter),
new NullStringifier($quoter),
new JsonParsableStringifier(),
]);
return $stringifier;
}
/**
* @param Stringifier[] $stringifiers
*/
public function setStringifiers(array $stringifiers): void
{
$this->stringifiers = [];
foreach ($stringifiers as $stringifier) {
$this->addStringifier($stringifier);
}
}
public function addStringifier(Stringifier $stringifier): void
{
$this->stringifiers[] = $stringifier;
}
public function stringify(mixed $raw, int $depth): ?string
{
foreach ($this->stringifiers as $stringifier) {
$string = $stringifier->stringify($raw, $depth);
if ($string === null) {
continue;
}
return $string;
}
return null;
}
}
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use DateTimeInterface;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use function sprintf;
final class DateTimeStringifier implements Stringifier
{
public function __construct(
private readonly Stringifier $stringifier,
private readonly Quoter $quoter,
private readonly string $format
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!$raw instanceof DateTimeInterface) {
return null;
}
return $this->quoter->quote(
sprintf(
'[date-time] (%s: %s)',
$raw::class,
$this->stringifier->stringify($raw->format($this->format), $depth + 1)
),
$depth
);
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use function is_float;
use function is_infinite;
final class InfiniteStringifier implements Stringifier
{
public function __construct(
private readonly Quoter $quoter
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!is_float($raw)) {
return null;
}
if (!is_infinite($raw)) {
return null;
}
return $this->quoter->quote(($raw > 0 ? '' : '-') . 'INF', $depth);
}
}
@@ -0,0 +1,32 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Stringifier;
use function json_encode;
use const JSON_PRESERVE_ZERO_FRACTION;
use const JSON_UNESCAPED_SLASHES;
use const JSON_UNESCAPED_UNICODE;
final class JsonParsableStringifier implements Stringifier
{
public function stringify(mixed $raw, int $depth): ?string
{
$string = json_encode($raw, (JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION));
if ($string === false) {
return null;
}
return $string;
}
}
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use JsonSerializable;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use function sprintf;
final class JsonSerializableStringifier implements Stringifier
{
public function __construct(
private readonly Stringifier $stringifier,
private readonly Quoter $quoter
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!$raw instanceof JsonSerializable) {
return null;
}
return $this->quoter->quote(
sprintf(
'[json-serializable] (%s: %s)',
$raw::class,
$this->stringifier->stringify($raw->jsonSerialize(), $depth + 1)
),
$depth
);
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use function is_float;
use function is_nan;
final class NanStringifier implements Stringifier
{
public function __construct(
private readonly Quoter $quoter
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!is_float($raw)) {
return null;
}
if (!is_nan($raw)) {
return null;
}
return $this->quoter->quote('NaN', $depth);
}
}
@@ -0,0 +1,31 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
final class NullStringifier implements Stringifier
{
public function __construct(
private readonly Quoter $quoter
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if ($raw !== null) {
return null;
}
return $this->quoter->quote('NULL', $depth);
}
}
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use function get_object_vars;
use function is_object;
use function sprintf;
final class ObjectStringifier implements Stringifier
{
public function __construct(
private readonly Stringifier $stringifier,
private readonly Quoter $quoter
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!is_object($raw)) {
return null;
}
return $this->quoter->quote(
sprintf(
'[object] (%s: %s)',
$raw::class,
$this->stringifier->stringify(get_object_vars($raw), $depth + 1)
),
$depth
);
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use function get_resource_type;
use function is_resource;
use function sprintf;
final class ResourceStringifier implements Stringifier
{
public function __construct(
private readonly Quoter $quoter
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!is_resource($raw)) {
return null;
}
return $this->quoter->quote(
sprintf(
'[resource] (%s)',
get_resource_type($raw)
),
$depth
);
}
}
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Stringifier;
use function is_object;
use function method_exists;
final class StringableObjectStringifier implements Stringifier
{
public function __construct(
private readonly Stringifier $stringifier
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!is_object($raw)) {
return null;
}
if (!method_exists($raw, '__toString')) {
return null;
}
return $this->stringifier->stringify($raw->__toString(), $depth);
}
}
@@ -0,0 +1,60 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use Throwable;
use function getcwd;
use function sprintf;
use function str_replace;
final class ThrowableStringifier implements Stringifier
{
public function __construct(
private readonly Stringifier $stringifier,
private readonly Quoter $quoter
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!$raw instanceof Throwable) {
return null;
}
return $this->quoter->quote(
sprintf(
'[throwable] (%s: %s)',
$raw::class,
$this->stringifier->stringify($this->getData($raw), $depth + 1)
),
$depth
);
}
/**
* @return mixed[]
*/
private function getData(Throwable $throwable): array
{
return [
'message' => $throwable->getMessage(),
'code' => $throwable->getCode(),
'file' => sprintf(
'%s:%d',
str_replace(getcwd() . '/', '', $throwable->getFile()),
$throwable->getLine()
),
];
}
}
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier\Stringifiers;
use Respect\Stringifier\Quoter;
use Respect\Stringifier\Stringifier;
use Traversable;
use function iterator_to_array;
use function sprintf;
final class TraversableStringifier implements Stringifier
{
public function __construct(
private readonly Stringifier $stringifier,
private readonly Quoter $quoter
) {
}
public function stringify(mixed $raw, int $depth): ?string
{
if (!$raw instanceof Traversable) {
return null;
}
return $this->quoter->quote(
sprintf(
'[traversable] (%s: %s)',
$raw::class,
$this->stringifier->stringify(iterator_to_array($raw), $depth + 1)
),
$depth
);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
/*
* This file is part of Respect/Stringifier.
* Copyright (c) Henrique Moody <henriquemoody@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Stringifier;
use Respect\Stringifier\Stringifiers\ClusterStringifier;
function stringify(mixed $value): string
{
static $stringifier;
if ($stringifier === null) {
$stringifier = ClusterStringifier::createDefault();
}
return $stringifier->stringify($value, 0) ?? '#ERROR#';
}