Search by

fghazaleh / cport

fghazaleh

Static, consistent wrappers for PHP's array, string and math functions

Package info

github.com/fghazaleh/cport

pkg:composer/fghazaleh/cport

Statistics

Installs: 70

Dependents: 0

Suggesters: 0

Stars: 5

Open Issues: 0

4.0.0 2026-11-10 23:21 UTC

This package is auto-updated.

Last update: 2026-11-14 17:53:44 UTC


README

cport wraps PHP's array, string and math functions in three static classes with one consistent API: Arr, Str and Math. Nobody remembers whether it is array_map($fn, $array) or array_filter($array, $fn), whether strpos returns false or 0, or what sqrt(-1) gives you. With cport you type Arr::, Str:: or Math::, let your IDE complete the name, and the arguments always come in the same order.

CI

composer require fghazaleh/cport

Requires PHP 8.5+ and ext-mbstring.

Rules every method follows

Rule Example
Subject first, always Arr::map($array, $fn), Arr::filter($array, $fn), Str::replace($subject, $search, $replace), Str::contains($haystack, $needle)
No array_ / str prefixes Arr::chunk(), Arr::unique(), Str::pad(), Str::repeat()
Nothing is changed in place. By-reference functions return the new value $sorted = Arr::sort($array), $rest = Arr::pop($array), Arr::push($array, 4)
null instead of false when nothing was found Arr::search(), Str::pos(), Str::str()
Throws instead of NAN, INF or a warning Math::sqrt(-1), Math::log(0), Str::hexToBin('zz')
UTF-8 by default for strings Str::length('héllo') === 5, Str::upper('é') === 'É'
Pure functions, no state, no objects all classes are final with a private constructor
use FG\Support\Arr;
use FG\Support\Math;
use FG\Support\Str;

Arr::map([1, 2, 3], fn ($n) => $n * 2);          // [2, 4, 6]
Arr::filter([1, 2, 3, 4], fn ($n) => $n > 2);    // [2 => 3, 3 => 4]
Arr::reduce([1, 2, 3], fn ($c, $n) => $c + $n, 0); // 6
Arr::sort([3, 1, 2]);                            // [1, 2, 3] (the argument is untouched)
Arr::search(['a', 'b'], 'z');                    // null, not false
Arr::first([]);                                  // null
Arr::find([1, 2, 3], fn ($n) => $n > 1);         // 2

Str::length('héllo');                            // 5
Str::pos('héllo wörld', 'w');                    // 6
Str::substr('héllo', 1, 3);                      // 'éll'
Str::pad('é', 5, '-', STR_PAD_BOTH);             // '--é--'
Str::wordwrap('The quick brown fox', 10, "\n", true);
Str::levenshtein('kitten', 'sitting');           // 3
Str::compare('a', 'b');                          // -1 (always -1, 0 or 1)

Math::round(2.5);                                // 3.0
Math::max(3, 9, 4);                              // 9
Math::sqrt(-1);                                  // throws InvalidArgumentException
Math::baseConvert('ff', 16, 2);                  // '11111111'
Math::decToHex(255);                             // 'ff'

What is wrapped

The complete list, with the PHP function behind every method and the functions that are deliberately not wrapped (and why), is in docs/FUNCTIONS.md.

Class Methods Covers
Arr 76 array_*, the sort family, range, count, in_array, implode, iterator_to_array, plus find, findKey, any, all, first, last (PHP 8.4/8.5 functions that work on every version)
Str 88 str*, substr*, mb_*, trim family, html*, *slashes, sprintf family, levenshtein, similar_text, wordwrap, hashes
Math 48 abs, round, log*, pow, sqrt, trigonometry, base conversion, number_format, mt_rand, random_int

Naming follows the PHP function without its prefix, in camelCase, for example array_key_first → Arr::keyFirst(), str_word_count → Str::wordCount(), bindec → Math::binToDec(). A few readability renames: in_array → Arr::contains(), strstr → Str::str(), substr_replace → Str::replaceSubstr(), rad2deg → Math::radToDeg().

Differences from PHP worth knowing

  • Comparisons (Str::compare*) always return -1, 0 or 1.
  • User-callback set functions (udiff, uintersect, diffUassoc, ...) take exactly one other array.
  • Arr::pop, shift, splice return the remaining array. Read the removed element first with Arr::last(), Arr::first() or Arr::slice().
  • Str::trim family supports .. ranges only for ASCII characters.
  • Str::soundex, metaphone, rot13, md5, sha1, crc32, countChars, span, cspan are byte based by nature.
  • Math::baseConvert and the other base functions lose precision above PHP_INT_MAX, like PHP.

Fluent classes: Collection, Text, Number, Chars, LazyCollection

Chainable, immutable value objects built on the static classes. They only add what PHP has no function for (snake, slug, pluck, groupBy, statistics, ...) plus a small set of chain basics; for everything else use Arr, Str and Math directly.

use FG\Support\Collection;
use FG\Support\Number;
use FG\Support\Text;

Collection::of($employees)
    ->filter(fn ($e) => $e['active'])
    ->sortBy('salary', descending: true)
    ->pluck('name')
    ->all();

Collection::of($orders)->groupBy('customer.id');       // ['c1' => [...], 'c2' => [...]]
Collection::of($orders)->average('total');             // 42.5, or null when empty

Text::of('Héllo Wörld')->slug()->toString();           // 'héllo-wörld'
Text::of('fooBar baz')->snake()->toString();           // 'foo_bar_baz'
Text::of('  some text ')->trim()->limit(4)->upper();   // Text('SOME...')

Number::of('19.99')->multiply(3)->round(1)->format(2); // '60.00'
Number::of(23)->toOrdinal();                           // '23rd'
Number::of(1)->percentOf(4)->value();                  // 25.0

Rules:

  • A fluent class returns its own type or a plain PHP value. Text::explode() returns an array, Collection::average() returns ?float, Collection::implode() returns a string. No fluent class depends on another one (a test enforces it); to keep chaining across classes, wrap the value: Collection::of($text->explode(',')).
  • They connect through PHP interfaces: Collection::of() and LazyCollection::of() accept any iterable, Text::of(), Number::of() and Chars::of() accept any Stringable.
  • Callbacks receive the value only. Methods that take a key accept a dot-notation key ('user.address.city') or a Closure.
  • Full method names, no aliases: plus, minus, multiply, divide, modulo, power, squareRoot.
Class Methods
Collection of, fromJson, all, count, isEmpty, isNotEmpty, first, last, get, has, contains, keys, values, map, filter, reject, reduce, merge, unique, sort, sortBy, pluck, groupBy, flatten, implode, sum, average, min, max, median, toJson
Text of, toString, length, isEmpty, isNotEmpty, equals, upper, lower, ucfirst, lcfirst, title, camel, studly, snake, kebab, slug, trim, ltrim, rtrim, pad, append, prepend, repeat, reverse, replace, remove, substr, limit, before, after, beforeLast, between, contains, startsWith, endsWith, wordCount, explode, words
Number of, value, toInt, toFloat, plus, minus, multiply, divide, modulo, power, squareRoot, abs, round, ceil, floor, clamp, compare, equals, isBetween, isEven, isOdd, isWhole, percentOf, format, toOrdinal

| Chars | of, fromCodes, count, isEmpty, at, first, last, contains, toArray, codes, frequency, filter, reject, map, reverse, unique, sort, take, skip, letters, digits, spaces, uppercase, lowercase, punctuation, toString | | LazyCollection | of, range, map, filter, reject, take, skip, chunk, values, reduce, first, isEmpty, count, all |

LazyCollection: generator pipelines

Nothing runs until you consume the result, so it works on large or infinite sources:

use FG\Support\LazyCollection;

LazyCollection::range(1, PHP_INT_MAX)
    ->filter(fn ($n) => $n % 2 === 0)
    ->map(fn ($n) => $n ** 2)
    ->take(3)
    ->values()
    ->all();                                  // [4, 16, 36]

LazyCollection::of($lines)->chunk(100);       // yields arrays of 100 lines, one at a time

A Closure source can be iterated more than once; a bare generator can only be consumed once.

Chars: the characters of a string

Works on characters (code points), not bytes:

use FG\Support\Chars;

Chars::of('Héllo, Wörld 42')->letters()->unique()->sort()->toString();  // 'HWdloréö'
Chars::of('héllo')->codes();                                           // [104, 233, 108, 108, 111]
Chars::of('abca')->frequency();                                        // ['a' => 2, 'b' => 1, 'c' => 1]
Chars::fromCodes([72, 233])->toString();                               // 'Hé'

Development

composer install
composer test      # PHPUnit
composer cs        # check code style (PHP-CS-Fixer, dry run)
composer cs:fix    # fix code style
composer check     # both

Each wrapper has at least one test that compares it with the native PHP function (where behaviour is identical) or with an explicit expected value (where it intentionally differs).