Search by

develia / commons

antonio-gil-espinosa

Common utility library

0.9.9 2026-09-18 09:02 UTC

README

Biblioteca de utilidades de bajo nivel para PHP (>= 8.2) que extiende el lenguaje con una sintaxis más moderna y robusta: colecciones (estilo LINQ), IO, web/HTTP, reflexión, fechas, matemáticas, paralelización y utilidades generales.

  • Namespace raíz: Develia\ (PSR-4, mapeado a src/).
  • Extensiones requeridas: mbstring, json, simplexml.

Instalación

composer require develia/commons

La librería incluye su propio autoloader (src/autoload.php) y es compatible con el autoloading de Composer.

Patrones de diseño

  • Try-Parse (inspirado en .NET): métodos tryX(..., &$output): bool que evitan excepciones y permiten control de flujo basado en booleanos (Str, Date, RequestPayload, From, Util).
  • Colecciones fluidas (LINQ style): Develia\From es el núcleo del procesamiento de datos:

    from($array)->filter(...)->map(...)->toArray();
    
  • Abstracción de entorno: Parallelizer y OS adaptan la librería al sistema operativo y a las extensiones disponibles (Swoole, Amp, ReactPHP).

Mapa de utilidades

  • From — operaciones de colecciones (filter/map/reduce/join/distinct/sortBy…).
  • Obj — utilidades exclusivas de objetos y clases: reflexión, toArray (objeto → array), getTraits/getInterfaces/getClass/getParentClass, isA/isSubclassOf/instantiate.
  • Util — utilidades de propósito general (no exclusivas de objetos): comprobaciones de tipo (isString, isArray, isObject, isNullOrEmpty, isIterable…), conversiones (toInt, toString, toFloat, toBoolean), clonación de cualquier valor (clone), acceso dinámico a elementos/propiedades de arrays u objetos (get/set/tryGet/trySet/assign), fromArray (array → stdClass), y helpers como clamp, compare, swap, hash, isBetween y repeat.
  • Str / Date / Math — utilidades de cadenas, fechas y matemáticas.
  • IO\Stream y IO/ — sistema de archivos y streams orientado a objetos.
  • Request / RequestPayload / Response / UploadedFile — capa HTTP.
  • Reflector / ClassInfo — introspección de clases, interfaces y traits.
  • Assert — precondiciones fail-fast con estrechamiento estático, composición y errores estructurados.

Obj vs Util

Obj agrupa únicamente las utilidades que operan exclusivamente sobre objetos y clases, mientras que las utilidades genéricas viven en Util:

use Develia\Obj;
use Develia\Util;

// Objetos/clases -> Obj
$data = Obj::toArray($entity);
$traits = Obj::getTraits($entity);

// Propósito general -> Util
if (Util::isString($value)) { /* ... */ }
$copy = Util::clone($entity, true);
$n = Util::clamp($n, 0, 100);

Aserciones fail-fast

Assert valida precondiciones sin acumular errores ni sustituir a un sistema de validación de formularios. Sus guardas de tipo estrechan valores en PHPStan y Psalm; nullOr() y all() componen una única regla, deteniéndose en el primer incumplimiento:

use Develia\Assert;

Assert::nullOr($alias, static fn(mixed $value) => Assert::isString($value));
Assert::all($roles, static fn(mixed $role) => Assert::isNotEmptyString($role));

$name = Assert::value($input, 'user.name')
    ->isString()
    ->isNotEmptyString()
    ->getValue();

Los fallos lanzan Develia\Exceptions\AssertionException, compatible con InvalidArgumentException, e incluyen el valor rechazado, la restricción y la ruta de propiedad opcional mediante sus accesores.

Solicitudes HTTP y método QUERY

Request::getPayload() distingue entre métodos que solo consultan la URL (GET, DELETE, HEAD y OPTIONS) y métodos con contenido. QUERY se trata como un método seguro e idempotente con cuerpo y admite JSON, XML, CSV, texto, application/x-www-form-urlencoded y multipart/form-data:

Request::getContentType() devuelve el media type normalizado y sin parámetros; Request::getHeader() resuelve nombres sin distinguir mayúsculas/minúsculas. Headers, tipo de contenido y body se capturan de forma diferida una sola vez por instancia.

use Develia\Request;
use Develia\RequestPayload;

$request = Request::current();
$payload = $request->getPayload();

if ($payload instanceof RequestPayload && $payload->has('xxxxx')) {
    $explicitamenteVacio = $payload->isEmpty('xxxxx');
    $valor = $payload->getString('xxxxx');
}

Request::getQuery() devuelve siempre un RequestPayload. Para métodos distintos de QUERY representa únicamente el query string de la URL. Para QUERY exige un Content-Type válido y combina los parámetros de la URL con un body JSON estructurado, URL-encoded o multipart. La combinación es superficial: una clave del body sustituye por completo la misma clave de la URL.

Por ejemplo, una petición QUERY /documents?scope=public&filter[status]=draft con body JSON {"filter":{"status":"published"},"limit":20} produce scope, filter y limit, pero filter contiene solo el árbol enviado en el body:

$query = Request::current()->getQuery();

$scope = $query->getString('scope');
$filter = $query->getArray('filter');
$limit = $query->getInt('limit');
$document = $query->getFile('document'); // Disponible con multipart/form-data.

XML, CSV, texto y tipos de medio desconocidos continúan disponibles mediante getPayload(), pero getQuery() los rechaza porque no existe una conversión inequívoca a pares clave/valor. Los archivos de un body multipart se conservan como UploadedFile en el payload combinado.

La presencia y el contenido se consultan por separado. Para xxxxx=&yyyy=2, has('xxxxx') e isEmpty('xxxxx') devuelven true; si solo se envía yyyy=2, ambos devuelven false. null, '' y [] son valores vacíos, pero 0, '0', false y los espacios no lo son.

RequestPayload conserva un snapshot de arrays por valor, accesible mediante getData(), y otro de archivos mediante getFiles(). Sus archivos deben estar normalizados como UploadedFile; no acepta objetos de datos ni registros $_FILES sin adaptar:

use Develia\RequestPayload;
use Develia\UploadedFile;

$file = UploadedFile::createFromPhpUpload($_FILES['document']);
$payload = new RequestPayload($_POST, ['document' => $file]);

RequestPayload::merge($first, $second, ...) combina datos y archivos por separado; en claves repetidas prevalece el último payload.

Para archivos creados al procesar un cuerpo multipart manualmente se usa UploadedFile::createFromTemporaryFile(). El antiguo constructor público de UploadedFile y Request::parse(&$form, &$files) se han eliminado; usa Request::getForm() y los accesores del payload. Los argumentos nombrados thousands_separator y decimals_separator pasan a llamarse thousandsSeparator y decimalsSeparator.

Tests

vendor/bin/phpunit test