digitalrevolution/utils
A PHP 8.3+ utility library of everyday helper classes, focused on array operations such as first/last, find, flatten, map, groupBy, diff, unique, remove, JSON conversion, and path fetching, plus iterable and string helpers.
Installation:
composer require digitalrevolution/utils
Add the namespace to your composer.json autoload or use it directly via use statements:
use DigitalRevolution\Utils\Arrays;
use DigitalRevolution\Utils\Assert;
use DigitalRevolution\Utils\Stringify;
use DigitalRevolution\Utils\Closures;
First Use Case:
Replace native array operations with safer, more expressive methods. For example, replace reset($array) with:
$firstItem = Arrays::first($array); // Throws exception if empty
$firstItemOrNull = Arrays::firstOrNull($array); // Returns null if empty
Need to normalize a list of IDs coming from a request or query string? Use the new toIntList:
$ids = Arrays::toIntList($request->input('ids', [])); // [1, 2, 3]
Where to Look First:
flatten, groupBy, fetchByPath, toIntList).Assert::notNull($value)->isInstanceOf(MyClass::class)).Stringify::value($complexObject)).Closures::unfold($closure)).Flattening Nested Arrays:
$flatArray = Arrays::flatten($nestedArray);
Useful for API responses or nested configurations.
Path-Based Access:
$value = Arrays::fetchByPath($array, ['user', 'profile', 'name']);
Arrays::assignByPath($array, ['user', 'profile', 'age'], 30);
Ideal for deeply nested Laravel config or request data.
Grouping and Mapping:
$grouped = Arrays::groupBy($users, fn($user) => $user['department']);
$mapped = Arrays::map($users, fn($user, $key) => strtoupper($user['name']));
Replace collect()->groupBy() or array_map() for type safety.
Casting to an Integer List (v1.34.0+):
$ids = Arrays::toIntList($request->input('ids', []));
$ids = Arrays::toIntList(explode(',', $request->query('ids', '')));
Ideal for normalizing query-string values, CSV imports, or form multi-selects into a consistent list<int> before passing them into whereIn(), repositories, or DTOs. Replaces array_map('intval', $values) boilerplate with a typed, single-purpose helper.
Safe Removal:
$filtered = Arrays::remove($items, fn($item) => $item->isArchived());
$filtered = Arrays::removeTypes($items, [null, 'string']);
Cleaner than manual array_filter with custom logic.
Fluent Chaining:
Assert::notNull($user)->isInstanceOf(User::class)->notFalse();
Replace if (!$user) throw new \RuntimeException(...) with self-documenting code.
Custom Error Messages:
Assert::greaterThan($age, 18, 'User must be at least 18 years old');
Useful in Laravel form requests or API validation.
Type-Specific Assertions:
Assert::isArray($config)->nonEmptyArray();
Assert::fileExists($path)->readable();
Validate Laravel config, file uploads, or environment variables.
Logging Complex Data:
\Log::debug('User data:', ['data' => Stringify::value($user)]);
Replace var_export or json_encode for human-readable output.
Template Rendering:
$template = "User: {user}, Status: {status}";
$rendered = str_replace(
['{user}', '{status}'],
[Stringify::value($user), Stringify::value($user->status)]
);
Service Initialization:
$service = fn() => new ExpensiveService();
$instance = Closures::unfold($service); // Executes once
Useful for Laravel service providers or dependency injection.
Memoization:
$cache = [];
$getCached = fn($key) => $cache[$key] ?? ($cache[$key] = fetchExpensiveData($key));
$value = Closures::unfold($getCached('key'));
Form Request Validation:
public function rules(): array {
return [
'age' => ['integer', 'min:18'],
// Custom validation via Assert:
'user' => ['required', 'array', 'min:1'],
];
}
public function withValidator($validator) {
$validator->after(function ($validator) {
Assert::notNull($this->user['id']);
Assert::greaterThan($this->user['age'], 0);
});
}
Service Providers:
public function register() {
$this->app->singleton(ExpensiveService::class, function () {
return Closures::unfold(fn() => new ExpensiveService());
});
}
Normalizing Request Input for Queries:
$ids = Arrays::toIntList($request->input('ids', []));
$users = User::whereIn('id', $ids)->get();
API Responses:
return response()->json([
'data' => Arrays::map($users, fn($user) => [
'id' => $user['id'],
'name' => Stringify::value($user['name']),
]),
]);
first/last vs firstOrNull/lastOrNull:
Always prefer *OrNull in Laravel controllers to avoid UndefinedArrayKeyException in views/blades.
$firstItem = Arrays::firstOrNull($array); // Safe for Blade
fetchByPath and assignByPath:
\InvalidArgumentException if path is invalid.Arrays::wrap($path) if path might be a scalar:
Arrays::fetchByPath($array, Arrays::wrap('user.profile'));
toIntList (new in v1.34.0):
array of scalar values (strings, numerics) — pass explode(',', ...) or $request->input(...) directly.Assert or a Form Request rule if input is untrusted (e.g. ['integer'] on ids.*).array_values() if you need to be certain, though the helper is intended to normalize this for you.Assert::isList(...) for asserting the shape you feed into whereIn() or repository methods.remove Performance:
Arrays::removeNull() is optimized (uses array_filter internally).array_filter + array_values if order matters.equals vs ==:
Arrays::equals($a, $b) ignores order and supports ComparableInterface.Exception Messages:
Assert::notNull($value, 'Custom error message');
Assert::greaterThan($age, 18, 'Age must be > {0}')).isList vs isArray:
isList checks for sequential arrays (no gaps in keys).Assert::isList($response['data']);
File Assertions:
fileExists and readable/writable are Laravel-friendly for storage paths:
Assert::fileExists(storage_path('app/logs/error.log'))->readable();
Nested Assertions:
Assert::notNull($user)->isInstanceOf(User::class)) require explicit message passing:
Assert::notNull($user, 'User not found')->isInstanceOf(User::class, 'Invalid user type');
Edge Cases:
Stringify::value(null) returns 'null'.Stringify::value([]) returns 'empty-array'.Stringify::value(new DateTime()) returns '2023-10-01T00:00:00+00:00'.JsonSerializable, uses json_encode($value, JSON_THROW_ON_ERROR).Debugging:
app/Exceptions/Handler.php for exception debugging:
report(new \RuntimeException(Stringify::value($context)));
unfold Behavior:
$service = fn() => new DatabaseConnection();
$connection = Closures::unfold($service); // Executes once
$service = fn() => new Service();
$instance1 = Closures::unfold($service); // Executes
$service = fn() => new AnotherService(); // Resets!
$instance2 = Closures::unfold($service); // Executes again
Assert methods. Enable them in phpstan.neon by including the package's extension:
includes:
- vendor/digitalrevolution/utils/phpstan-extension.neon
phpmd/phpmd to v3. This is a dev dependency used by the package itself for its own code quality checks. It has no runtime impact on your application and requires no action on your side.composer update digitalrevolution/utils is enough; you can start using Arrays::toIntList immediately.How can I help you explore Laravel packages today?