PHPackages                             cosmologist/gears - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. cosmologist/gears

ActiveLibrary[Database &amp; ORM](/categories/database)

cosmologist/gears
=================

PHP-Gears - handy helper library for PHP and Symfony

4.0.0(11mo ago)84.3k11MITPHP

Since Nov 24Pushed 1mo ago3 watchersCompare

[ Source](https://github.com/Cosmologist/Gears)[ Packagist](https://packagist.org/packages/cosmologist/gears)[ RSS](/packages/cosmologist-gears/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (2)DependenciesVersions (24)Used By (1)

PHP-Gears - handy helper library for PHP and Symfony
====================================================

[](#php-gears---handy-helper-library-for-php-and-symfony)

- [Installation](#installation)
- Common
    - [Array functions](#array-functions)
    - [Cache utils](#cache-utils)
    - [Callable functions](#callable-functions)
    - [Class functions](#class-functions)
    - [File object](#file-object)
    - [File functions](#file-functions)
    - [Number functions](#number-functions)
    - [Object functions](#object-functions)
    - [String functions](#string-functions)
- [Broadway](#broadway-utils)
- Doctrine
    - [Common utils](#doctrine-common-utils)
    - [DBAL utils](#doctrine-dbal-utils)
- Symfony
    - [ExpressionLanguage utils](#symfony-expressionlanguage-utils)
    - [Form utils](#symfony-forms-utils)
    - [Framework utils](#symfony-framework-utils)
    - [Messenger utils](#symfony-messenger-utils)
    - [PropertyAccess utils](#symfony-propertyaccess-utils)
    - [Security utils](#symfony-security-utils)
    - [Test utils](#symfony-test-utils)
    - [Twig utils](#twig-utils)
    - [Validator utils](#symfony-validator-utils)
- [Value Objects](#value-objects)
    - [Identifier](#value-object-that-represents-an-identifier)
    - [Identifier-UUID](#value-object-that-represents-a-uuid-identifier)
    - [Identifier-UUID-Hybrid](#value-object-that-represents-a-hybrid-uuid-identifier)

Installation
------------

[](#installation)

```
composer require cosmologist/gears

```

Array functions
---------------

[](#array-functions)

### Get an item from the array by key

[](#get-an-item-from-the-array-by-key)

```
ArrayType::get(['fruit' => 'apple', 'color' => 'red'], 'fruit'); // 'apple'
ArrayType::get(['fruit' => 'apple', 'color' => 'red'], 'weight'); // null
ArrayType::get(['fruit' => 'apple', 'color' => 'red'], 'weight', 15); // 15
ArrayType::get(['apple', 'red'], -1); // 'red'
```

### Adds a value to an array with a specific key only if key not presents in an array

[](#adds-a-value-to-an-array-with-a-specific-key-only-if-key-not-presents-in-an-array)

It's more intuitive variant to `$array += [$key => $value];`

```
$array = ['fruit' => 'apple'];
ArrayType::touch($array, 'color', 'red']); // ['fruit' => 'apple', 'color' => 'red'];
ArrayType::touch($array, 'fruit', 'banana']); // ['fruit' => 'apple'];
```

### Push element onto the end of array and returns the modified array

[](#push-element-onto-the-end-of-array-and-returns-the-modified-array)

```
$a = [1,2];
ArrayType::push($a, 3); // [1,2,3]
```

### Prepend element to the beginning of an array and returns the modified array

[](#prepend-element-to-the-beginning-of-an-array-and-returns-the-modified-array)

```
$a = [2,3];
ArrayType::unshift($a, 1); // [1,2,3]
```

### Calculate the average of values in an array (array\_avg)

[](#calculate-the-average-of-values-in-an-array-array_avg)

```
ArrayType::average([1, 2, 3]); // 3
```

### Check if array is associative

[](#check-if-array-is-associative)

```
ArrayType::checkAssoc([1, 2, 3]); // false
ArrayType::checkAssoc(['foo' => 'bar']); // true
```

### Check if a value exists in an array

[](#check-if-a-value-exists-in-an-array)

```
ArrayType::contains(array $list, mixed $item);
```

### Get the first item from an iterable that optionally matches a given condition

[](#get-the-first-item-from-an-iterable-that-optionally-matches-a-given-condition)

Unlike array\_shift() or reset(), this function safely handles any iterable and allows filtering via a callback.

```
// Get the first item of any iterable
ArrayType::first([1, 2, 3]); // returns 1

// Find first even number
ArrayType::first([1, 3, 4, 6], fn($x) => $x % 2 === 0); // returns 4

// Use named argument for optional parameter
ArrayType::first([1, 2, 3], condition: fn($x) => $x > 1); // returns 2

// Returns null if no match or empty
ArrayType::first([], condition: fn($x) => $x > 0); // returns null
```

### Get the last item from an iterable that optionally matches a given condition.

[](#get-the-last-item-from-an-iterable-that-optionally-matches-a-given-condition)

Unlike end() or array\_pop(), this function works with any iterable and supports filtering via a callback.

```
// Get the last item of any iterable
ArrayType::last([1, 2, 3]); // returns 3

// Find last even number
ArrayType::last([1, 4, 3, 6], fn($x) => $x % 2 === 0); // returns 6

// Use named argument for optional parameter
ArrayType::last([1, 2, 3], condition: fn($x) => $x < 3); // returns 2

// Returns null if no match or empty
ArrayType::last([], condition: fn($x) => $x > 0); // returns null
```

### Inserts an array after the key

[](#inserts-an-array-after-the-key)

```
ArrayType::insertAfter(['a' => 1, 'c' => 3], 'a', ['b' => 2]); // ['a' => 1, 'b' => 2, 'c' => 3]
// If the key doesn't exist
ArrayType::insertAfter(['a' => 1, 'b' => 2], 'c', ['foo' => 'bar']); // ['a' => 1, 'b' => 2, 'foo' => 'bar']
```

### Inserts an array before the key

[](#inserts-an-array-before-the-key)

```
ArrayType::insertBefore(['a' => 1, 'c' => 3], 'c', ['b' => 2]); // ['a' => 1, 'b' => 2, 'c' => 3]
// If the key doesn't exist
ArrayType::insertBefore(['a' => 1, 'b' => 2], 'c', ['foo' => 'bar']); // ['foo' => 'bar', 'a' => 1, 'b' => 2]
```

### Convert list of items to ranges

[](#convert-list-of-items-to-ranges)

```
ArrayType::ranges([1, 3, 7, 9]); // [[1, 3], [3, 7], [7, 9]]
```

### Unset array item by value

[](#unset-array-item-by-value)

```
ArrayType::unsetValue(['a', 'b', 'c'], 'b'); // ['a', 'c']
```

### Calculate the standard deviation of values in an array

[](#calculate-the-standard-deviation-of-values-in-an-array)

```
ArrayType::deviation([1, 2, 1]); // float(0.4714045207910317)
```

### Cast to an array

[](#cast-to-an-array)

Behavior for different types:

- array - returns as is
- iterable - converts to a native array (`iterator_to_array()`)
- another - creates an array with argument (\[value\])

```
ArrayType::toArray($value);
```

### Get the array encoded in json

[](#get-the-array-encoded-in-json)

If encoded value is false, true or null then returns empty array.
JSON\_THROW\_ON\_ERROR always enabled.

```
ArrayType::fromJson($json): array;
```

Cache utils
-----------

[](#cache-utils)

### Generate a deterministic cache key for arbitrary parameters

[](#generate-a-deterministic-cache-key-for-arbitrary-parameters)

By serializing into a JSON string.
You can use the cache value calculation function as part of the cache key (just pass its closure along with the other parameters).

```
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;

$cacheKey = CacheUtils::generateKey('foo', 123, ['foo' => 'bar'], (object) ['bar' => 'baz'], $identifier);
// or
$cacheKey = CacheUtils::generateKey('my-cache-key', $identifier);
// or
$cacheKey = CacheUtils::generateKey(computingFunction(...), $identifier);

// On cache misses, a callback is called that should return the missing value.
$callback = fn() => computingFunction($identifier);
// or
$callback = fn(ItemInterface $item) use ($identifier) {
    $item->expiresAfter(3600);
    ...
    computingFunction($identifier);
}
$value = $cache->get($cacheKey, $callback);
```

Callable functions
------------------

[](#callable-functions)

### Determine if a callable a closure

[](#determine-if-a-callable-a-closure)

```
CallableType::isClosure(fn($foo) => $foo); // bool(true)
CallableType::isClosure('foo'); // bool(false)
CallableType::isClosure([$foo, 'bar']); // bool(false)
CallableType::isClosure('Foo\Bar::baz'); // bool(false)
```

### Determine if a callable a function

[](#determine-if-a-callable-a-function)

```
CallableType::isFunction(fn($foo) => $foo); // bool(false)
CallableType::isFunction('foo'); // bool(true)
CallableType::isFunction([$foo, 'bar']); // bool(false)
CallableType::isFunction('Foo\Bar::baz'); // bool(false)
```

### Determine if a callable a method

[](#determine-if-a-callable-a-method)

```
CallableType::isMethod(fn($foo) => $foo); // bool(false)
CallableType::isMethod('foo'); // bool(false)
CallableType::isMethod([$foo, 'bar']); // bool(true)
CallableType::isMethod('Foo\Bar::baz'); // bool(true)
```

### Determine if a callable a static method

[](#determine-if-a-callable-a-static-method)

```
CallableType::isStaticMethod(fn($foo) => $foo); // bool(false)
CallableType::isStaticMethod('foo'); // bool(false)
CallableType::isStaticMethod([$foo, 'bar']); // bool(false)
CallableType::isStaticMethod('Foo\Bar::baz'); // bool(true)
```

### Get suitable reflection implementation for the callable

[](#get-suitable-reflection-implementation-for-the-callable)

```
CallableType::reflection(fn($foo) => $foo); // object(ReflectionFunction)
CallableType::reflection('foo'); // object(ReflectionFunction)
CallableType::reflection([$foo, 'bar']); // object(ReflectionMethod)
CallableType::reflection('Foo\Bar::baz'); // object(ReflectionMethod)
```

Class functions
---------------

[](#class-functions)

### Get the class or an object class short name

[](#get-the-class-or-an-object-class-short-name)

```
ClassType::short('Foo\Bar'); // "Bar"
ClassType::short(Foo\Bar::class); // "Bar"
ClassType::short(new Foo\Bar()); // "Bar"
```

### Get the class and the parent classes

[](#get-the-class-and-the-parent-classes)

```
namespace Foo;

class Bar {};
class Baz extends Foo {};
...
ClassType::selfAndParents('Foo\Bar'); // ["Foo\Bar"]
ClassType::selfAndParents(Foo\Bar::class); // ["Foo\Bar"]
ClassType::selfAndParents(new Foo\Bar()); // ["Foo\Bar"]
ClassType::selfAndParents('Foo\Baz'); // ["Foo\Baz", "Foo\Bar"]
ClassType::selfAndParents(Foo\Baz::class); // ["Foo\Baz", "Foo\Bar"]
ClassType::selfAndParents(new Foo\Baz()); // ["Foo\Baz", "Foo\Bar"]
```

### Retrieve a list of parent classes (and optionally interfaces) for a given class or object.

[](#retrieve-a-list-of-parent-classes-and-optionally-interfaces-for-a-given-class-or-object)

This function extends PHP's built-in class\_parents() by optionally including the class itself and implemented interfaces.

```
namespace Foo;

class Bar {};
class Baz extends Bar implements Stringable {};

ClassType::parents(Baz::class) // [Baz::class, Bar::class]
ClassType::parents(Baz::class, withSelf: false) // [Bar::class]
ClassType::parents('MyClass', withSelf: true, withInterfaces: true) // [Baz::class, Bar::class, Stringable::class]
```

### Get the class and the parent classes

[](#get-the-class-and-the-parent-classes-1)

```
namespace Foo;

class Bar {};
class Baz extends Foo {};
...
ClassType::selfAndParents('Foo\Bar'); // ["Foo\Bar"]
ClassType::selfAndParents(Foo\Bar::class); // ["Foo\Bar"]
ClassType::selfAndParents(new Foo\Bar()); // ["Foo\Bar"]
ClassType::selfAndParents('Foo\Baz'); // ["Foo\Baz", "Foo\Bar"]
ClassType::selfAndParents(Foo\Baz::class); // ["Foo\Baz", "Foo\Bar"]
ClassType::selfAndParents(new Foo\Baz()); // ["Foo\Baz", "Foo\Bar"]
```

### Get the corresponding basic enum case dynamically from variable

[](#get-the-corresponding-basic-enum-case-dynamically-from-variable)

Basic enumerations does not implement from() or tryFrom() methods, but it is possible to return the corresponding enum case using the constant() function.

```
ClassType::enumCase(FooEnum::class, 'bar');
```

File object
-----------

[](#file-object)

File object-oriented implementation

### Serializes data and immediately writes it to the file corresponding to the object

[](#serializes-data-and-immediately-writes-it-to-the-file-corresponding-to-the-object)

```
$storage = new File('storage/abc.json');
$storage->serialize($fooObject);
```

### Unserializes data from the file corresponding to the object

[](#unserializes-data-from-the-file-corresponding-to-the-object)

```
$storage = new File('storage/abc.json');
$storage->serialize($fooObject);
$barObject = $storage->unserialize(MyObject::class); // object(MyObject)
```

File functions
--------------

[](#file-functions)

### Get the extension of a file name

[](#get-the-extension-of-a-file-name)

```
FileType::extension('/foo/bar.baz'); // 'baz'
FileType::extension('/foo/bar'); // ''
```

### Write a string to a file and create the file directory recursively if it does not exist

[](#write-a-string-to-a-file-and-create-the-file-directory-recursively-if-it-does-not-exist)

```
FileType::put('/foo/bar.txt', 'baz');
```

### Get the path to the file with $name inside the system temporary directory

[](#get-the-path-to-the-file-with-name-inside-the-system-temporary-directory)

```
FileType::temporary('foo.txt'); // '/tmp/foo.txt'
```

### Determine if the path an absolute path

[](#determine-if-the-path-an-absolute-path)

```
FileType::isAbsolutePath('C:/foo'); true
FileType::isAbsolutePath('C:\\bar'); true
FileType::isAbsolutePath('foo/bar'); false
FileType::isAbsolutePath('/foo/bar'); true
FileType::isAbsolutePath('\\foo\\bar'); true
```

### Join the paths into one and fix the directory separators

[](#join-the-paths-into-one-and-fix-the-directory-separators)

```
FileType::joinPaths('a/', '/b/', '\\c', 'd'); // Return a/b/c/d
```

### Fix the directory separators (remove duplicates and replace with the current system directory separator)

[](#fix-the-directory-separators-remove-duplicates-and-replace-with-the-current-system-directory-separator)

```
FileType::fixPath('/foo//bar\baz'); '/foo/bar/baz'
```

### Guess the file extensions of the file

[](#guess-the-file-extensions-of-the-file)

```
FileType::guessExtensions('/foo/bar.txt'); // ['txt']
FileType::guessExtensions('/foo/bar.jpg'); // ['jpeg', 'jpg', 'jpe', 'jfif']
```

### Guess the file extension of the file

[](#guess-the-file-extension-of-the-file)

```
FileType::guessExtension('/foo/bar.txt'); // 'txt'
FileType::guessExtension('/foo/bar.jpg'); // 'jpeg'
```

### Guess the mime-type of the file

[](#guess-the-mime-type-of-the-file)

```
FileType::guessMime('/foo/bar.txt'); // 'text/plain'
FileType::guessMime('/foo/bar.jpg'); // 'image/jpeg'
```

Number functions
----------------

[](#number-functions)

### Parse a float or integer value from the argument

[](#parse-a-float-or-integer-value-from-the-argument)

Remove all characters except digits, +-.,eE from the argument and returns result as the float value or NULL if the parser fails.

```
NumberType::parse(" 123 "); // int(123)
NumberType::parse(" 123.45 "); // float(123.45)
NumberType::parse(" 123.00 "); // int(123)
```

### Parse a float value from the argument

[](#parse-a-float-value-from-the-argument)

Remove all characters except digits, +-.,eE from the argument and returns result as the float value or NULL if the parser fails.

```
NumberType::parseFloat(" 123 "); // float(123)
NumberType::parseFloat(" 123.45 "); // float(123.45)
```

### Parse a integer value from the argument

[](#parse-a-integer-value-from-the-argument)

Remove all characters except digits, plus and minus sign and returns result as the integer value or NULL if the parser fails.

```
NumberType::parseInteger(" 123 "); // int(123)
NumberType::parseFloat(" 123.45 "); // int(12345)
```

### Returns fractions of the float value

[](#returns-fractions-of-the-float-value)

```
NumberType::fractions(123.45); // float(0.45)
NumberType::parseFloat(123); // float(0)
```

### Checks if the value is odd

[](#checks-if-the-value-is-odd)

```
NumberType::odd(2); // false
NumberType::odd(3); // true
```

### Checks if the value is even

[](#checks-if-the-value-is-even)

```
NumberType::even(2); // true
NumberType::even(3); // false
```

### Round to nearest multiple

[](#round-to-nearest-multiple)

```
NumberType::roundStep(50, 5); // 50
NumberType::roundStep(52, 5); // 50
NumberType::roundStep(53, 5); // 55
```

### Round down to nearest multiple

[](#round-down-to-nearest-multiple)

```
NumberType::floorStep(50, 5); // 50
NumberType::floorStep(52, 5); // 50
NumberType::floorStep(53, 5); // 50
```

### Round up to nearest multiple

[](#round-up-to-nearest-multiple)

```
NumberType::ceilStep(50, 5); // 50
NumberType::ceilStep(52, 5); // 55
NumberType::ceilStep(53, 5); // 55
```

### Spell out

[](#spell-out)

```
// Current locale used
NumberType::spellout(123.45); // one hundred twenty-three point four five

// Specific locale used
NumberType::spellout(123.45, 'ru'); // сто двадцать три целых сорок пять сотых
```

### Division with zero tolerance

[](#division-with-zero-tolerance)

```
NumberType::divideSafely(1, 0); // null
NumberType::divideSafely(1, null); // null
NumberType::divideSafely(1, 0, 'zero'); // 'zero'
```

### Percent calculation

[](#percent-calculation)

The first argument is a value for calculating the percentage. The second argument is a base value corresponding to 100%.

```
NumberType::percentage(10, 100); // 10
NumberType::percentage(100, 100); // 100
NumberType::percentage(200, 100); // 200
```

### Unsign a number

[](#unsign-a-number)

A negative value will be converted to zero, positive or zero value will be returned unchanged.

```
NumberType::unsign(-1); // 0
NumberType::unsign(-0.99); // 0
NumberType::unsign(0); // 0
NumberType::unsign(0.99); // 0.99
NumberType::unsign(1); // 1
```

### Converts a number to string with sign.

[](#converts-a-number-to-string-with-sign)

```
NumberType::toStringWithSign(-1); // "-1"
NumberType::toStringWithSign(1); // "+1"
NumberType::toStringWithSign(0); // "0"
```

Object functions
----------------

[](#object-functions)

### Read the value at the end of the property path of the object graph

[](#read-the-value-at-the-end-of-the-property-path-of-the-object-graph)

```
ObjectType::get($person, 'address.street');
```

Uses Symfony PropertyAccessor

### Read the value of internal object property (protected and private)

[](#read-the-value-of-internal-object-property-protected-and-private)

Read [ocramius](https://ocramius.github.io/blog/accessing-private-php-class-members-without-reflection/)

```
ObjectType::getInternal($object, $property);
```

### Get the values of the property path of the object recursively

[](#get-the-values-of-the-property-path-of-the-object-recursively)

Read [ocramius](https://ocramius.github.io/blog/accessing-private-php-class-members-without-reflection/)

```
$grandfather = new Person(name: 'grandfather');
$dad = new Person(name: 'dad', parent: $grandfather);
$i = new Person(name: 'i', parent: $dad);

ObjectType::getRecursive($i, 'parent'); // [Person(dad), Person(grandfather)]
```

### Set the value at the end of the property path of the object graph

[](#set-the-value-at-the-end-of-the-property-path-of-the-object-graph)

```
ObjectType::set($person, 'address.street', 'Abbey Road');
```

Uses Symfony PropertyAccessor

### Write the value to internal object property (protected and private)

[](#write-the-value-to-internal-object-property-protected-and-private)

Read [ocramius](https://ocramius.github.io/blog/accessing-private-php-class-members-without-reflection/)

```
ObjectType::setInternal($object, $property, $value);
```

### Call the internal object method (protected and private) and returns result

[](#call-the-internal-object-method-protected-and-private-and-returns-result)

Read [ocramius](https://ocramius.github.io/blog/accessing-private-php-class-members-without-reflection/)

```
ObjectType::callInternal($object, $method, $arg1, $arg2, $arg3, ...);
```

### Get a string representation of the object or enum

[](#get-a-string-representation-of-the-object-or-enum)

- Result of \_\_toString method if presents
- String value of case for the BackedEnum
- Name of case for the UnitEnum
- or generated string like "FQCN@spl\_object\_id"

PHP default behavior: if the method is not defined, an error (`Object of class X could not be converted to string`) is triggered.

```
namespace Foo;

class Bar {
}
class BarMagicMethod {
    public function __toString(): string {
        return 'Bar';
    }
}
enum BazUnitEnum {
    case APPLE;
}
enum BazStringBackedEnum: string {
    case APPLE = 'apple';
}
enum BazIntBackedEnum: int {
    case APPLE = 1;
}

ObjectType::toString(new Foo); // 'Foo/Bar@1069'
ObjectType::toString(new FooMagicMethod); // 'Foo'
ObjectType::toString(BazUnitEnum::APPLE); // 'APPLE'
ObjectType::toString(BazStringBackedEnum::APPLE); // '1'
```

### Cast an object or a FQCN to FQCN

[](#cast-an-object-or-a-fqcn-to-fqcn)

Returns the result of `__toString` or null if the method is not defined.
PHP default behavior: if the method is not defined, an error (`Object of class X could not be converted to string`) is triggered.

```
ObjectType::toClassName($objectOrClass): string;
```

String functions
----------------

[](#string-functions)

### Determine if a given string contains a given substring

[](#determine-if-a-given-string-contains-a-given-substring)

```
StringType::contains('Foo', 'Bar'); // false
StringType::contains('FooBar', 'Bar'); // true
```

### Simple symmetric decryption of a string with a key (using libsodium)

[](#simple-symmetric-decryption-of-a-string-with-a-key-using-libsodium)

```
StringType::decrypt(StringType::encrypt('The sensitive string', 'qwerty123456'), 'qwerty123456'); // 'The sensitive string'
```

### Simple symmetric encryption of a string with a key (using libsodium)

[](#simple-symmetric-encryption-of-a-string-with-a-key-using-libsodium)

```
StringType::encrypt('The sensitive string', 'qwerty123456');
```

### Convenient way to perform a regular expression match

[](#convenient-way-to-perform-a-regular-expression-match)

Default behaviour like preg\_match\_all(..., ..., PREG\_SET\_ORDER)

```
StringType::regexp('a1b2', '\S(\d)'); // [0 => [0 => 'a1', 1 => '1'], 1 => [0 => 'b2', 1 => '2']]
```

Exclude full matches from regular expression matches

```
StringType::regexp('a1b2', '\S(\d)', true); // [0 => [0 => '1'], 1 => [0 => '2']]
```

Get only first set from regular expression matches (exclude full matches)

```
StringType::regexp('a1b2', '(\S)(\d)', true, true); // [0 => 'a', 1 => '1']
```

Get only first match of each set from regular expression matches (exclude full matches)

```
StringType::regexp('a1b2', '(\S)(\d)', true, false, true); // [0 => 'a', 1 => 'b']
```

Get only first match of the first set from regular expression matches as single scalar value

```
StringType::regexp('a1b2', '(\S)(\d)', true, true, true); // 'a'
```

### Replace first string occurrence in a string

[](#replace-first-string-occurrence-in-a-string)

```
StringType::replaceFirst('name name name', 'name', 'title'); // 'title name name'
```

### Find the position of a substring within a string with support for case sensitivity, reverse search, and multibyte encodings

[](#find-the-position-of-a-substring-within-a-string-with-support-for-case-sensitivity-reverse-search-and-multibyte-encodings)

This method improves upon PHP's native string position functions (like strpos, stripos, etc.) by eliminating common pitfalls:

- it returns null instead of false when the substring is not found — preventing type confusion
- supports multibyte and 8-bit encodings

```
// Basic search in a UTF-8 string
$pos = StringType::position('Hello 世界', '世'); // returns 6

// Case-insensitive search
$pos = StringType::position('Hello World', 'WORLD', searchCaseSensitive: false); // returns 6

// Find last occurrence of substring
$pos = StringType::position('abcbc', 'bc', searchFromEnd: true); // returns 3

// Returns null when substring is not found (not false)
$pos = StringType::position('test', 'x'); // returns null

// Disable multibyte mode for ASCII-only strings
$pos = StringType::position('simple text', 'text', multibyteEncoding: false); // returns 6
```

### Find the substring before the first (or last) occurrence of a given needle

[](#find-the-substring-before-the-first-or-last-occurrence-of-a-given-needle)

This function extracts the part of the haystack string that appears before the specified needle.
It supports case-sensitive and case-insensitive searches, allows searching from the end of the string, and handles multibyte characters correctly by default.

```
// Returns 'Hello ' (before 'World' in a case-sensitive search)
StringType::strBefore('Hello World', 'World');

// Returns null because 'world' is not found with case-sensitive search
StringType::strBefore('Hello World', 'world');

// Returns 'Hello ' due to case-insensitive search
StringType::strBefore('Hello World', 'world', searchCaseSensitive: false);

// Returns 'Hello Wor' (before last 'l', searching from the end)
StringType::strBefore('Hello World', 'l', searchFromEnd: true);

// Returns 'Привет ' (correctly handles Cyrillic characters)
StringType::strBefore('Привет Мир', 'Мир');

// Returns null when needle is not found
StringType::strBefore('Test', 'xyz');
```

### Find the substring after the first (or last) occurrence of a given needle

[](#find-the-substring-after-the-first-or-last-occurrence-of-a-given-needle)

This function extracts the portion of the haystack string that comes after the specified needle. It supports case-sensitive and case-insensitive searches, allows searching from the end of the string, and properly handles multibyte characters by default.

```
// Returns 'World' (after 'Hello ' in a case-sensitive search)
StringType::strAfter('Hello World', 'Hello ');

// Returns null because 'hello ' is not found when case-sensitive
StringType::strAfter('Hello World', 'hello ');

// Returns 'World' due to case-insensitive search
StringType::strAfter('Hello World', 'hello ', searchCaseSensitive: false);

// Returns 'd' (after the last occurrence of 'l', searching from the end)
StringType::strAfter('Hello World', 'l', searchFromEnd: true);

// Returns 'Мир' (correctly handles multibyte UTF-8 characters)
StringType::strAfter('Привет Мир', 'Привет ');

// Returns null when needle is at the end and nothing follows
StringType::strAfter('Test', 'st');
```

### Wrap string

[](#wrap-string)

```
StringType::wrap('target', '/'); // '/target/'
```

### Guess the MIME-type of the string data

[](#guess-the-mime-type-of-the-string-data)

```
StringType::guessMime('foo bar'); // "text/plain"
StringType::guessMime(file_get_content("foo.jpg")); // "image/jpeg"
```

### Guess the file extension from the string data.

[](#guess-the-file-extension-from-the-string-data)

```
StringType::guessExtension('foo bar'); // "txt"
StringType::guessExtension(file_get_content("foo.jpg")); // "jpeg"
```

### Check if a string is a binary string

[](#check-if-a-string-is-a-binary-string)

```
StringType::isBinary('Foo bar baz'); // false
```

### Convert string to CamelCase

[](#convert-string-to-camelcase)

```
StringType::toCamelCase('string like this'); // 'StringLikeThis'
StringType::toCamelCase('string_like_this'); // 'StringLikeThis'
```

### Convert string to snake\_case

[](#convert-string-to-snake_case)

```
StringType::toSnakeCase('StringLikeThis'); // 'string_like_this'
StringType::toSnakeCase('string Like this'); // 'string_like_this'
```

### ltrim()/rtrim()/trim() replacements supports UTF-8 chars in the charlist

[](#ltrimrtrimtrim-replacements-supports-utf-8-chars-in-the-charlist)

Use these only if you are supplying the charlist optional arg and it contains UTF-8 characters. Otherwise trim will work normally on a UTF-8 string.

```
trim('«foo»', '»'); // "�foo"
StringType::trim('«foo»', '»'); // "«foo"
```

### Split text into sentences

[](#split-text-into-sentences)

```
StringType::sentences('Fry me a Beaver. Fry me a Beaver! Fry me a Beaver? Fry me Beaver no. 4?! Fry me many Beavers... End);
```

returns

```
[
  [0] => 'Fry me a Beaver.',
  [1] => 'Fry me a Beaver!',
  [2] => 'Fry me a Beaver?',
  [3] => 'Fry me Beaver no. 4?!',
  [4] => 'Fry me many Beavers...',
  [5] => 'End'
]
```

### Split text into words

[](#split-text-into-words)

We assume that words are separated by whitespace.
Words can contain letters, numbers, and other symbols, but a word must begin and end with only a letter or number.

```
StringType::words('Fry me many Beavers... End'); // ['Fry', 'me', 'many', 'Beavers', 'End']

// Another cases
StringType::words('Roland'); // ['Roland']
StringType::words('Roland TB303'); // ['Roland', 'TB303']
StringType::words('Roland TB-303'); // ['Roland', 'TB-303']
StringType::words('Roland TB-303.'); // ['Roland', 'TB-303']
StringType::words('Roland TB-303â'); // ['Roland', 'TB-303â']
StringType::words('âRoland TB-303'); // ['âRoland', 'TB-303']
StringType::words('Roland - TB303'); // ['Roland', 'TB303']
StringType::words('Roland – TB303'); // ['Roland', 'TB303']
StringType::words("Roland'â - TB303"); // ["Roland'â", 'TB303']
StringType::words('"Roland" - TB303'); // ['Roland', 'TB303']
StringType::words('R.O.L.A.N.D. - TB303'); // ['R.O.L.A.N.D', 'TB303']
```

### Remove word from text

[](#remove-word-from-text)

```
StringType::unword('Remove word from text', 'word'); // 'Remove from text'
```

Broadway utils
--------------

[](#broadway-utils)

### The DbalEventStoreSelectable, inherited from DBALEventStore, allows you to select messages from the store based on specified criteria.

[](#the-dbaleventstoreselectable-inherited-from-dbaleventstore-allows-you-to-select-messages-from-the-store-based-on-specified-criteria)

The criteria specified using `Doctrine\Common\Collections\Criteria`.

```
$criteria = new Criteria();
$criteria->where(new Expr\Comparison('uuid', Expr\Comparison::EQ, $order->getValue()));
$criteria->orWhere(new Expr\Comparison('_related->"$[*]"', Expr\Comparison::MEMBER_OF, $order->getValue()));

$this->eventStore->walk(
    $criteria,
    function (DomainMessage $domainMessage) {
        ...
    }
);
```

Doctrine common utils
---------------------

[](#doctrine-common-utils)

### DoctrineUtils activation

[](#doctrineutils-activation)

Manually instance a DoctrineUtils

```
public function __construct(private Doctrine\Persistence\ManagerRegistry $doctrine)
{
    $doctrineUtils = new Cosmologist\Gears\Doctrine\DoctrineUtils($doctrine);
}
```

Register DoctrineUtils as a service with Symfony DI

```
# config/services.yaml
services:
    _defaults:
        autowire: true

    Cosmologist\Gears\Doctrine\DoctrineUtils:
```

### Get metadata for a persistent object or a persistent object class

[](#get-metadata-for-a-persistent-object-or-a-persistent-object-class)

```
$doctrineUtils->getClassMetadata(new App\Entity\User()); // object(ClassMetadata)
$doctrineUtils->getClassMetadata(App\Entity\User::class); // object(ClassMetadata)
$doctrineUtils->getClassMetadata(new App\Controller\FooController())); // null
$doctrineUtils->getClassMetadata(App\Controller\FooController::class); // null
```

### Get real class of a persistent object (resolve a proxy class)

[](#get-real-class-of-a-persistent-object-resolve-a-proxy-class)

```
$doctrineUtils->getRealClass(Proxies\__CG__\App\Entity\User::class); // 'App\Entity\User'
$doctrineUtils->getRealClass(new Proxies\__CG__\App\Entity\User()); // 'App\Entity\User'
$doctrineUtils->getRealClass(App\Entity\User::class); // 'App\Entity\User'
$doctrineUtils->getRealClass(new App\Entity\User()); // 'App\Entity\User'
$doctrineUtils->getRealClass(new App\Controller\FooController()); // null
$doctrineUtils->getRealClass(App\Controller\FooController::class); // null
```

### Check if an object, or an object class persistent (managed by the Doctrine)

[](#check-if-an-object-or-an-object-class-persistent-managed-by-the-doctrine)

```
$doctrineUtils->isManaged(new MyEntity()); // true
$doctrineUtils->isManaged(new stdClass()); // false
```

### Get an identifier field name of the Doctrine object

[](#get-an-identifier-field-name-of-the-doctrine-object)

```
$doctrineUtils->getSingleIdentifierField(new MyEntityWithSingleIdentifier(id: 1000)); // 'id'
$doctrineUtils->getSingleIdentifierField(new MyEntityWithMultipleIdentifiers()); // \Assert\InvalidArgumentException
$doctrineUtils->getSingleIdentifierField(new stdClass); // \Assert\InvalidArgumentException
```

### Get an identifier value of the Doctrine object

[](#get-an-identifier-value-of-the-doctrine-object)

```
$doctrineUtils->getSingleIdentifierValue(new MyEntityWithSingleIdentifier(id: 1000)); // 1000
$doctrineUtils->getSingleIdentifierValue(new MyEntityWithMultipleIdentifiers()); // \Assert\InvalidArgumentException
$doctrineUtils->getSingleIdentifierValue(new stdClass); // \Assert\InvalidArgumentException
```

### Merge multiple Doctrine\\Common\\Collections\\Criteria into a new one

[](#merge-multiple-doctrinecommoncollectionscriteria-into-a-new-one)

```
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr;

DoctrineUtils::mergeCriteria(
    new Criteria(new Expr\Comparison('status', Expr\Comparison::EQ, 'new')),
    new Criteria(new Expr\Comparison('type', Expr\Comparison::NEQ, 'foo'))
);
```

### Add a join to a QueryBuilder with support of the nested join (e.g. "contact.user.type")

[](#add-a-join-to-a-querybuilder-with-support-of-the-nested-join-eg-contactusertype)

```
$qb = $entityManager->getRepository(Company::class)->createQueryBuilder('company');
DoctrineUtils::join($qb, 'contact.user.type');
// equivalent to
$qb
  ->join('company.contact', 'contact')
  ->join('contact.user', 'user')
  ->join('user.type', 'type');
```

### Add a join to a QueryBuilder once and returns an alias of join

[](#add-a-join-to-a-querybuilder-once-and-returns-an-alias-of-join)

```
$qb = $entityManager->getRepository(Company::class)->createQueryBuilder('contact');

// Adds a join and returns an alias of added join
DoctrineUtils::joinOnce($qb, 'contact.user', 'u1'); // "u1"

// If a join with specified parameters exists then only returns an alias of existed join
DoctrineUtils::joinOnce($qb, 'contact.user', 'u2'); // "u1"
```

### Get a target class name of a given association path recursively (e.g. "contact.user")

[](#get-a-target-class-name-of-a-given-association-path-recursively-eg-contactuser)

```
$doctrineUtils->getAssociationTargetClassRecursive('AppBundle/Entity/Company', 'contact.user'); // 'AppBundle/Entity/User'
```

Doctrine DBAL utils
-------------------

[](#doctrine-dbal-utils)

### Apply Doctrine Collection expressions (Criteria) to the DBAL QueryBuilder

[](#apply-doctrine-collection-expressions-criteria-to-the-dbal-querybuilder)

The converter implementation incomplete - a limited number of comparisons currently supported.

```
$criteria = new Criteria();
$criteria->where(new Expr\Comparison('uuid', Expr\Comparison::EQ, $order->getValue()));
$criteria->orWhere(new Expr\Comparison('_related->"$[*]"', Expr\Comparison::MEMBER_OF, $order->getValue()));

$queryBuilder = $this->connection->createQueryBuilder();
$visitor      = new DbalExpressionVisitor($queryBuilder);

$queryBuilder
    ->select('*')
    ->from($this->tableName)
    ->andWhere($criteria->getWhereExpression()->visit($visitor))
    ->executeQuery();
```

Symfony ExpressionLanguage utils
--------------------------------

[](#symfony-expressionlanguage-utils)

### Create an ExpressionFunction from a callable

[](#create-an-expressionfunction-from-a-callable)

```
ExpressionFunctionUtils::fromCallable('Foo\Bar::baz'); // object(ExpressionFunction)
```

For example, this can be useful for injecting simple objects (like ValueObject) into a Symfony service container

```
class AppExtension extends Extension
{
    #[Override]
    public function load(array $configs, ContainerBuilder $container)
    {
        $container->addExpressionLanguageProvider(new class implements ExpressionFunctionProviderInterface {
            public function getFunctions(): array {
                return [ExpressionFunctionUtils::fromCallable([WalletIdentifier::class, 'create'], 'walletId')];
            }
        });

        $container
            ->getDefinition(OrderService::class)
            ->setArgument('$wallet', expr('walletId(13)'));
    }
}
```

Symfony Forms utils
-------------------

[](#symfony-forms-utils)

### Convert domain model constraint violation to the form constraint violation

[](#convert-domain-model-constraint-violation-to-the-form-constraint-violation)

It's maybe useful when you validate your model from form on the domain layer and want to map violations to the form.

```
use Cosmologist\Gears\Symfony\Form\FormUtils;
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper;
use Symfony\Component\Validator\Exception\ValidationFailedException;

if ($form->isSubmitted()) {
     try {
         return $this->handler->create($form->getData());
     } catch (ValidationFailedException $exception) {
         $violationMapper = new ViolationMapper();
         foreach ($exception->getViolations() as $domainViolation) {
             $violationMapper->mapViolation(FormUtils::convertDomainViolationToFormViolation($domainViolation), $form);
         }
     }
}

return $form->createView();
```

### Trait with a method implementing DataMapperInterface::mapDataToForms with default behavior

[](#trait-with-a-method-implementing-datamapperinterfacemapdatatoforms-with-default-behavior)

This is convenient for mapping of form data to a model via *DataMapperInterface::mapFormsToData()*, for example, to create a model via a constructor, in this case, the mapping of model data to a form via *DataMapperInterface::mapDataToForms()* will remain unchanged, and you cannot not define it, since it is required by the *DataMapperInterface*.

```
use Cosmologist\Gears\Symfony\Form\DataFormsMapperDefaultTrait;

class TransactionFormType extends AbstractType implements DataMapperInterface
{
    use DataFormsMapperDefaultTrait;

    #[Override]
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class)
            ->setDataMapper($this);
    }

    #[Override]
    public function mapFormsToData(Traversable $forms, mixed &$viewData): void
    {
        $forms = iterator_to_array($forms);
        $viewData = new Contact($forms['name']->getData());
    }
```

Symfony Framework utils
-----------------------

[](#symfony-framework-utils)

### Configure your Symfony application as a bundle using service container extension and configuration

[](#configure-your-symfony-application-as-a-bundle-using-service-container-extension-and-configuration)

```
namespace App;

use App\DependencyInjection\AppExtension;
use Cosmologist\Gears\Symfony\Framework\AppExtension\AppExtensionKernelInterface;
use Cosmologist\Gears\Symfony\Framework\AppExtension\RegisterAppExtensionKernelTrait;
use Override;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;

class Kernel extends BaseKernel implements AppExtensionKernelInterface
{
    use MicroKernelTrait;
    use RegisterAppExtensionKernelTrait;

    #[Override]
    public function getAppExtension(): ExtensionInterface
    {
        return new AppExtension();
    }
}
```

### Forwards the request to another controller that matches the passed URI.

[](#forwards-the-request-to-another-controller-that-matches-the-passed-uri)

Like `Symfony\Bundle\FrameworkBundle\Controller\Controller::forward`, but for URIs.

```
use Cosmologist\Gears\Symfony\Framework\Controller\ControllerUtils;

ControllerUtils::forwardByUri('/blog/my-post');
```

Symfony Messenger utils
-----------------------

[](#symfony-messenger-utils)

### Assert that a symfony messenger command (a command bus message) execution will throw an exception

[](#assert-that-a-symfony-messenger-command-a-command-bus-message-execution-will-throw-an-exception)

```
class FooTest extends KernelTestCase
{
    use MessengerTestUtilsTrait;

    protected function setUp(): void
    {
        self::bootKernel();

        // A MessengerTestUtilsTrait needs your command bus
        $this->commandBus = $this->getContainer()->get('command.bus');;
    }

    public function testBar() {
        $this->assertCommandShouldFail(new FooCommand);
        $this->assertCommandShouldFail(new FooCommand, BarException::class);
    }
}
```

### Symfony Messenger transport to redispatch messages on kernel.terminate event

[](#symfony-messenger-transport-to-redispatch-messages-on-kernelterminate-event)

It a convenient way to speed up your app response to clients by scheduling hard tasks after the server response, thanks to the `kernel.terminate` event.
When run an application from the CLI, the `kernel.terminate` event not generated, in this case the events handled on the `console.terminate` event.

Firstly, you should enable this transport:

```
# config/services.yaml
services:
    _defaults:
        autoconfigure: true

    Cosmologist\Gears\Symfony\Messenger\Transport\KernelTerminate\KernelTerminateTransportFactory:
```

Configure a messenger:

```
# config/packages/messenger.yaml
framework:
    messenger:

        transports:
            terminate: symfony-kernel-terminate://

        routing:
            App\Event\FooEvent: terminate

# Use "sync://" transport instead "symfony-kernel-terminate://" for tests
when@test:
  framework:
    messenger:
      transports:
        terminate: sync://
```

and

```
$this->messenger->dispatch(new App\Event\FooEvent('bar'));
// or
$this->messengerBus->dispatch(new App\Event\FooEvent('bar'));
```

Symfony PropertyAccess utils
----------------------------

[](#symfony-propertyaccess-utils)

### Get the values of the property path of the object or of the array recursively

[](#get-the-values-of-the-property-path-of-the-object-or-of-the-array-recursively)

```
use Cosmologist\Gears\Symfony\PropertyAccessor\RecursivePropertyAccessor;

$grandfather = new Person(name: 'grandfather');
$dad = new Person(name: 'dad', parent: $grandfather);
$i = new Person(name: 'i', parent: $dad);

(new RecursivePropertyAccessor())->getValue($i, 'parent'); // [Person(dad), Person(grandfather)]
```

Symfony Security utils
----------------------

[](#symfony-security-utils)

### Command for interactively configuring ACLs

[](#command-for-interactively-configuring-acls)

A simple yet convenient alternative to the standard `acl:set` command from AclBundle — no need to remember command semantics, user and object classes, worry about escaping, etc. every time you need to add access rights.
The command allows you to interactively select a user identity and object identity from existing ACLs.
If you want to create an ACL from a new user/object identity, you should pass the appropriate arguments to the command.

[![](https://github.com/cosmologist/gears/raw/master/doc/symfony_acl_interactive_command.gif?raw=true)](https://github.com/cosmologist/gears/blob/master/doc/symfony_acl_interactive_command.gif?raw=true)

Enable the command:

```
# config/services.yaml
services:
  _defaults:
    autowire: true
    autoconfigure: true

  Cosmologist\Gears\Symfony\Security\Acl\AclInteractiveCommand:
    bind:
      $aclConnection: '@doctrine.dbal.security_connection'
      $aclProvider: '@security.acl.provider'
      $aclTables:
        table_classes: '%security.acl.dbal.class_table_name%'
        table_sid: '%security.acl.dbal.sid_table_name%'
```

### A SuperUserRoleVoter brings a ROLE\_SUPER\_USER, which effectively bypasses any, and all security checks

[](#a-superuserrolevoter-brings-a-role_super_user-which-effectively-bypasses-any-and-all-security-checks)

#### Enable the ROLE\_SUPER\_USER

[](#enable-the-role_super_user)

```
# config/services.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    Cosmologist\Gears\Symfony\Security\Voter\SuperUserRoleVoter:
```

#### Check if ROLE\_SUPER\_USER granted (e.g. inside a controller)

[](#check-if-role_super_user-granted-eg-inside-a-controller)

```
class FooController extends AbstractController
{
    public function barAction(): Response
    {
        $this->denyAccessUnlessGranted(SuperUserRoleVoter::ROLE_SUPER_USER);
        ...
    }
}
```

Symfony Test utils
------------------

[](#symfony-test-utils)

### Add a specified HTTP-header to the kernel-browser request

[](#add-a-specified-http-header-to-the-kernel-browser-request)

```
use Cosmologist\Gears\Symfony\Test\TestUtils;

class FooTest extends WebTestCase
{
    protected function testBar(): void
    {
        $browser = self::createClient();
        TestUtils::addHeader($browser, 'User-Agent', 'Symfony KernelBrowser');
        ...
    }
}
```

Twig utils
----------

[](#twig-utils)

### A convenient way to render HTML attributes in Twig templates

[](#a-convenient-way-to-render-html-attributes-in-twig-templates)

```
{% set linkAttrs = {
    'href': 'https://github.com/Cosmologist/Gears',
    'class': ['important', 'disabled'],
    'style': {'display': 'block', 'color': 'green'},
    'data-attribute': 'bar',
    'data-json': {'foo': 'bar'}} %}

Click me

rendered as:
Click me

Use the "merge" filter to manage the attribute dictionary.
{% set linkAttrs = linkAttrs | merge({'class': 'disabled'}) %}

The "class" attribute may contain null values - they will be filtered out.
Click me - Click me

Non-unique values of the "class" attribute will be grouped.
Click me - Click me

You can pass the "class" attribute as a scalar.
Click me - Click me

You can pass the "style" attribute as a scalar.
Click me - Click me

You can pass the "style" attribute as a list.
Click me - Click me
```

Don't forget to enable this extension

```
# config/services.yaml
services:
    _defaults:
        autoconfigure: true

    Cosmologist\Gears\Twig\GearsUtilsExtension:
```

### Twig-based pagination (Bootstrap friendly)

[](#twig-based-pagination-bootstrap-friendly)

```
{% include '@Gears/pagination.html.twig' with { page: current_page, count: items_total, limit: items_per_page } %}

{# Parameters:
   * page (int) : The current page you are in
   * limit (int): Number of records to display per page
   * count (int): Total count of records
   * currentFilters (array)(optional) : associative array that contains route-arguments #}
```

Before use, you should register `@Gears` as Twig namespace

```
# config/packages/twig.yaml
twig:
    paths:
      '%kernel.project_dir%/vendor/cosmologist/gears/src/Twig/Pagination/Resources/views': Gears
```

and declare GearsTwigExtension

```
# config/services.yaml
services:
    _defaults:
        autoconfigure: true

    Cosmologist\Gears\Twig\GearsUtilsExtension:
```

Symfony Validator utils
-----------------------

[](#symfony-validator-utils)

### Simple and convenient way instance of ValidationFailedException with single ConstraintViolation

[](#simple-and-convenient-way-instance-of-validationfailedexception-with-single-constraintviolation)

```
use Cosmologist\Gears\Symfony\Validator\ValidationFailedException;

ValidationFailedException::violate($foo, "Foo with invalid bar");
ValidationFailedException::violate($foo, "Foo with invalid {{ bar }}", compact('bar'));
ValidationFailedException::violate($foo, "Foo with invalid bar", propertyPath: 'bar');
```

Value Objects
-------------

[](#value-objects)

### Value Object that represents an identifier

[](#value-object-that-represents-an-identifier)

```
class ProductIdentifier extends IdentifierAbstract {}

$p1 = new ProductIdentifier(123);
$p1->getValue(); // 123

$p2 = new ProductIdentifier('string-id');
$p2->getValue(); // 'string-id'

$p1->equals($p2); // bool(false)
$p1->equals(new ProductIdentifier(123)); // bool(true)
$p1->equals(123); // bool(true)
```

### Value Object that represents a UUID-identifier

[](#value-object-that-represents-a-uuid-identifier)

```
class ProductIdentifier extends IdentifierUuidAbstract {}

// Create UUID-identifier from value
$product = new ProductIdentifier('70b3738c-dec5-40a1-a992-bdadb3e33f9d'); // object(ProductIdentifier)

// Create UUID-identifier without value validation (default behaviour)
$product = new ProductIdentifier('123'); // object(ProductIdentifier)

// Create UUID-identifier with value validation
$product = new ProductIdentifier('123', validate: true); // InvalidArgumentException

// Create UUID-identifier with auto-generated value (UUID v4)
$product = new ProductIdentifier(); // // object(ProductIdentifier)

// IdentifierUuidAbstract extends IdentifierAbstract so also you can also call
$product->getValue(); // string('2b29a26d-ce2a-41a1-bcb7-41858ae4820f')
// and
$product->equals('2b29a26d-ce2a-41a1-bcb7-41858ae4820f'); // bool(true)
```

### Value Object that represents a hybrid UUID-identifier

[](#value-object-that-represents-a-hybrid-uuid-identifier)

The hybrid UUID-Identifier Value Object allows encoding up to two numeric values in a human-readable format.

This can convenient, for example, when a system works with UUIDs, but certain entities still rely on classic incremental identifiers. This hybrid UUID implementation may be used to hold 1–2 integer values that can be extracted from it.

```
$userEntity->getId(); // 12345
$uuid = new UserIdentifier($userEntity->getId()); // 00012345-0000-8aaa-bbbb-cccdddeeefff
$uuid->getPrimaryValue(); // 12345

```

It also supports storing a secondary value to identify nested or aggregated data:

```
$userEntity->getId(); // 12345
$userPhoto = $userEntity->getRandomPhoto();
$userPhoto->getNumber(); // 25
$uuid = new UserPhotoIdentifier($userEntity->getId(), $userPhoto->getNumber()); // 00012345-0025-8aaa-bbbb-cccdddeeefff
$uuid->getPrimaryValue(); // 12345
$uuid->getSecondaryValue(); // 25

```

Hybrid identifiers are highly readable for humans, especially within context. From the example above, it’s immediately clear that this refers to photo #25 of user #12345.

The hybrid UUID identifier follows this structure: `01234567-0890-8aaa-bbbb-cccdddeeefff`

- `1234567` — Encodes the primary integer identifier, supporting values from 0 to 99,999,999 (approximately uint26).
- `890` — Encodes the optional secondary identifier, supporting values from 0 to 9,999 (approximately uint13).
- `8` — UUID specification version.
- `aaa-bbbb-cccdddeeefff` — A suffix unique to each identifier implementation, defined and returned by the method IdentifierUuidHybridAbstract::suffix().

This technique is made possible by leveraging the *UUID v8* (*custom UUID*) specification.

###  Health Score

50

—

FairBetter than 96% of packages

Maintenance72

Regular maintenance activity

Popularity25

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity73

Established project with proven stability

 Bus Factor1

Top contributor holds 65.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~124 days

Recently: every ~540 days

Total

23

Last Release

353d ago

Major Versions

1.2.0 → 2.0.02018-11-27

2.7.3 → 3.0.02019-07-17

3.0.0 → 4.0.02025-05-30

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/966525?v=4)[Cosmologist](/maintainers/Cosmologist)[@Cosmologist](https://github.com/Cosmologist)

---

Top Contributors

[![Cosmologist](https://avatars.githubusercontent.com/u/966525?v=4)](https://github.com/Cosmologist "Cosmologist (30 commits)")[![4562448](https://avatars.githubusercontent.com/u/70571767?v=4)](https://github.com/4562448 "4562448 (16 commits)")

---

Tags

doctrinehelperlibraryphpsymfonyutilities

### Embed Badge

![Health badge](/badges/cosmologist-gears/health.svg)

```
[![Health](https://phpackages.com/badges/cosmologist-gears/health.svg)](https://phpackages.com/packages/cosmologist-gears)
```

###  Alternatives

[doctrine/orm

Object-Relational-Mapper for PHP

10.2k285.3M6.2k](/packages/doctrine-orm)[jdorn/sql-formatter

a PHP SQL highlighting library

3.9k115.1M102](/packages/jdorn-sql-formatter)[illuminate/database

The Illuminate Database package.

2.8k52.4M9.4k](/packages/illuminate-database)[ramsey/uuid-doctrine

Use ramsey/uuid as a Doctrine field type.

90440.3M211](/packages/ramsey-uuid-doctrine)[reliese/laravel

Reliese Components for Laravel Framework code generation.

1.7k3.4M16](/packages/reliese-laravel)[wildside/userstamps

Laravel Userstamps provides an Eloquent trait which automatically maintains `created\_by` and `updated\_by` columns on your model, populated by the currently authenticated user in your application.

7511.7M13](/packages/wildside-userstamps)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
