tobento / service-collection
Fluent and convenient wrapper classes for working with arrays of data.
Requires
- php: >=8.4
- tobento/service-macro: ^2.0
- tobento/service-support: ^2.0
Requires (Dev)
- phpunit/phpunit: ^12.3
- vimeo/psalm: ^6.13
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
The Collection Service provides fluent and convenient wrapper classes for working with arrays of data.
Table of Contents
Getting started
Add the latest version of the collection service running this command.
composer require tobento/service-collection
Requirements
- PHP 8.4 or greater
Highlights
- Framework-agnostic, will work with any project
- Decoupled design
Documentation
Collection
| Method | Description | Dot Notation Supported |
|---|---|---|
| add() | Add an item value if it does not exist or is null. | yes |
| all() | Get all items. | - |
| any() | Check if at least one of the items exists. | yes |
| combine() | Combine item values with item keys. | no |
| count() | Counts the items. | - |
| delete() | Delete an item by key. | yes |
| empty() | Check whether collection or an item is empty. | yes |
| except() | Get all items except the keys specified. | yes |
| filter() | Filter items. | - |
| first() | Get first item passing a given truth test. | - |
| flatten() | Flatten array to single level array. | - |
| get() | Get an item value by key. | yes |
| has() | Check if an item by key exists. | yes |
| keys() | Gets the item keys. First array level only. | no |
| last() | Get last item passing a given truth test. | - |
| lenientTyped() | Returns the items as lenient typed data. | yes |
| map() | Map over each of the items. | - |
| merge() | Merge items. | no |
| mergeRecursive() | Merge items recursive. | no |
| only() | Get only the items from the keys specified. | yes |
| onlyPresent() | Get only the items present from the keys specified. | yes |
| replace() | Replace the items. | - |
| replaceRecursive() | Replace the items recursive. | - |
| set() | Set an item value by key. | yes |
| setAll() | Set all items. | no |
| strictTyped() | Returns the items as strict typed data. | yes |
| toArray() | Converts the collection into a plain PHP array. | - |
| toJson() | Converts the collection into a JSON serialized string. | - |
| union() | Same as merge, except existing items got not overwritten. | - |
| values() | Gets the item values. | - |
add()
Add an item value if it does not exist or is null.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ])) ->add('sku', 'car') ->add('title', 'Car New') ->add('meta.color', 'blue') ->all(); /* Array ( [key] => car [title] => Car [sku] => car [meta] => Array ( [color] => blue ) ) */
all()
Get all items from the collection.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->all(); /* Array ( [key] => car [title] => Car ) */
any()
Check if at least one of the items exists.
use Tobento\Service\Collection\Collection; $exists = (new Collection([ 'key' => 'car', 'title' => 'Car', 'meta' => [ 'color' => 'blue' ] ]))->any('foo', 'meta.color'); var_dump($exists); // bool(true) $exists = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->any('bar', 'foo'); var_dump($exists); // bool(false)
combine()
Combine item values with item keys.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key', 'title', ]))->combine(['car', 'Car'])->all(); /* Array ( [key] => car [title] => Car ) */
count()
Counts the items.
use Tobento\Service\Collection\Collection; $count = (new Collection([ 'key', 'title', ]))->count(); var_dump($count); // int(2)
delete()
Delete an item by key.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car', 'meta' => [ 'color' => 'red' ] ]))->delete('meta.color')->all(); /* Array ( [key] => car [title] => Car [meta] => Array ( ) ) */
empty()
Check whether collection or an item is empty.
use Tobento\Service\Collection\Collection; $empty = (new Collection([ 'key' => 'car', 'title' => 'Car', 'meta' => [ 'color' => 'red' ] ]))->empty('meta.color'); var_dump($empty); // bool(false) $empty = (new Collection([ 'key' => 'car', 'title' => '' ]))->empty('title'); var_dump($empty); // bool(true) // check if collection is empty $empty = (new Collection([ 'key' => 'car', 'title' => '' ]))->empty(); var_dump($empty); // bool(false)
except()
Get all items except the keys specified.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car', 'meta' => [ 'color' => 'red', 'weight' => 1500, ] ]))->except(['key', 'meta.weight'])->all(); /* Array ( [title] => Car [meta] => Array ( [color] => red ) ) */
filter()
Filter items.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key', 'title', ]))->filter(fn($value, $key) => $key >= 1)->all(); /* Array ( [1] => title ) */
first()
Get first item passing a given truth test.
use Tobento\Service\Collection\Collection; // by a truth test $first = (new Collection([ 2, 8, 9 ]))->first(fn($value) => $value > 2); var_dump($first); // int(8) // by a truth test with a fallback $first = (new Collection([ 2, 8, 9 ]))->first(fn($value) => $value > 12, 15); var_dump($first); // int(15) // without truth test $first = (new Collection([ 2, 8, 9 ]))->first(); var_dump($first); // int(2)
flatten()
Flatten array to single level array.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car', 'meta' => [ 'foo' => 'bar' ] ]))->flatten()->all(); /* Array ( [0] => car [1] => Car [2] => bar ) */
get()
Get an item value by key. For strict typed values without casting, use strictTyped().
use Tobento\Service\Collection\Collection; $value = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->get('title'); var_dump($value); // string(3) "Car" // With fallback. If the value exist but does not match the fallback type, // it returns the fallback instead. $value = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->get('title', ['foo']); var_dump($value); // array(1) { [0]=> string(3) "foo" } // using dot notation for array depth $value = (new Collection([ 'key' => 'car', 'title' => 'Car', 'meta' => [ 'color' => 'red' ] ]))->get('meta.color'); var_dump($value); // string(3) "red"
has()
Check if an item by key exists.
use Tobento\Service\Collection\Collection; $has = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->has('title', 'key'); var_dump($has); // bool(true) $has = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->has('title', 'foo'); var_dump($has); // bool(false) // using dot notation for array depth $has = (new Collection([ 'key' => 'car', 'title' => 'Car', 'meta' => [ 'color' => 'red' ] ]))->has('title', 'meta.color'); var_dump($has); // bool(true)
keys()
Gets the item keys. First array level only.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->keys()->all(); /* Array ( [0] => key [1] => title ) */ // With a search value $all = (new Collection([ 'blue', 'red', 'blue', 'green' ]))->keys('blue', strict: false)->all(); /* Array ( [0] => 0 [1] => 2 ) */
last()
Get last item passing a given truth test.
use Tobento\Service\Collection\Collection; // by a truth test $last = (new Collection([ 2, 8, 9 ]))->last(fn($value) => $value < 7); var_dump($last); // int(2) // by a truth test with a fallback $last = (new Collection([ 2, 8, 9 ]))->last(fn($value) => $value > 12, 15); var_dump($last); // int(15) // without truth test $last = (new Collection([ 2, 8, 9 ]))->last(); var_dump($last); // int(9)
lenientTyped()
Returns the items as Lenient Typed Data. Values with the requested type are valid, and strings which exactly represent an int, float or bool.
use Tobento\Service\Collection\Collection; $typed = new Collection([ 'page' => '2', 'newsletter' => 'on', ])->lenientTyped(); var_dump($typed->int('page', 1)); // int(2) var_dump($typed->bool('newsletter')); // bool(true)
map()
Map over each of the items.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->map(fn($value, $key) => strtoupper($value))->all(); /* Array ( [key] => CAR [title] => CAR ) */
merge()
Merge items.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->merge(['foo' => 'bar', 'title' => 'Car VW'])->all(); /* Array ( [key] => car [title] => Car VW [foo] => bar ) */
mergeRecursive()
Merge items recursive.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->mergeRecursive(['foo' => 'bar', 'title' => 'Car VW'])->all(); /* Array ( [key] => car [title] => Array ( [0] => Car [1] => Car VW ) [foo] => bar ) */
only()
Get only the items from the keys specified.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car', 'meta' => [ 'color' => 'red', 'weight' => 1500, ] ]))->only(['title', 'meta.color'])->all(); /* Array ( [title] => Car [meta] => Array ( [color] => red ) ) */ // Define a default value. $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->only(['title', 'foo'], 'default value')->all(); /* Array ( [title] => Car [foo] => default value ) */
onlyPresent()
Get only the items present from the keys specified.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car', 'meta' => [ 'color' => 'red', 'weight' => 1500, ] ]))->onlyPresent(['title', 'meta.color', 'foo'])->all(); /* Array ( [title] => Car [meta] => Array ( [color] => red ) ) */
replace()
Replace the items.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->replace(['title' => 'Car VW', 'foo' => 'bar'])->all(); /* Array ( [key] => car [title] => Car VW [foo] => bar ) */
replaceRecursive()
Replace the items recursive.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car', 'colors' => [ 'blue', 'red' ] ]))->replaceRecursive(['colors' => [0 => 'green']])->all(); /* Array ( [key] => car [title] => Car [colors] => Array ( [0] => green [1] => red ) ) */
set()
Set an item value by key.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->set('color', 'red')->all(); /* Array ( [key] => car [title] => Car [color] => red ) */ // using dot notation for array depth $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->set('meta.color', 'red')->all(); /* Array ( [key] => car [title] => Car [meta] => Array ( [color] => red ) ) */
setAll()
Set all items.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->setAll(['title' => 'foo'])->all(); /* Array ( [title] => foo ) */
strictTyped()
Returns the items as Strict Typed Data. Only values which already have the requested type are valid.
use Tobento\Service\Collection\Collection; $typed = new Collection([ 'order' => ['comment' => 'Hi', 'quantity' => '3'], ])->strictTyped(); var_dump($typed->string('order.comment')); // string(2) "Hi" var_dump($typed->int('order.quantity', 1)); // int(1) ('3' is a string, so the default is returned)
The typed data is a snapshot of the current items. Changing the collection afterwards does not change the typed data.
toArray()
Converts the collection into a plain PHP array.
use Tobento\Service\Collection\Collection; $array = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->toArray(); /* Array ( [key] => car [title] => Car ) */
toJson()
Converts the collection into a JSON serialized string.
use Tobento\Service\Collection\Collection; $json = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->toJson(); // {"key":"car","title":"Car"}
union()
Same as merge, except existing items got not overwritten.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->union(['title' => 'foo', 'foo' => 'bar'])->all(); /* Array ( [key] => car [title] => Car [foo] => bar ) */
values()
Gets the item values.
use Tobento\Service\Collection\Collection; $all = (new Collection([ 'key' => 'car', 'title' => 'Car' ]))->values()->all(); /* Array ( [0] => car [1] => Car ) */
Typed Data
Typed data is for reading values of a known type from uncertain data such as request data, cookies, stored JSON or cart data. Each typed getter returns the value if it is valid for the type, otherwise the default. Values are never cast.
There are two implementations of the TypedDataInterface::class:
| Class | Valid values | Use for |
|---|---|---|
| Strict Typed Data | Only values which already have the requested type. | Data with known types, such as stored JSON, cart data or config. |
| Lenient Typed Data | Values with the requested type, and strings which exactly represent an int, float or bool. | Data where everything arrives as string, such as request data, cookies or imported CSV data. |
Methods
| Method | Description | Dot Notation Supported |
|---|---|---|
new(array $data) |
Returns a new instance with the given data. | - |
has($key) |
Check if a key exists. | yes |
get($key, $default = null) |
Returns the value without any type check. | yes |
string($key, $default = '') |
Returns the value as string or the default. | yes |
int($key, $default = 0) |
Returns the value as int or the default. | yes |
float($key, $default = 0.0) |
Returns the value as float or the default. | yes |
bool($key, $default = false) |
Returns the value as bool or the default. | yes |
array($key, $default = []) |
Returns the value as array or the default. | yes |
list($key, $default = []) |
Returns the value as list (array with sequential keys starting at 0) or the default. | yes |
all() |
Returns all data. | - |
Sub sections
Use the new() method to create typed data from a part of the data:
$order = $typed->new($typed->array('order')); var_dump($order->string('comment'));
If the key is missing or not an array, the new typed data is empty and every getter returns its default.
Strict Typed Data
Only values which already have the requested type are valid.
use Tobento\Service\Collection\StrictTypedData; $data = new StrictTypedData([ 'order' => ['comment' => 'Hi', 'quantity' => '3'], 'gift' => true, ]); var_dump($data->string('order.comment')); // string(2) "Hi" var_dump($data->string('order.missing', 'none')); // string(4) "none" var_dump($data->int('order.quantity', 1)); // int(1) ('3' is a string, so the default is returned) var_dump($data->bool('gift')); // bool(true) var_dump($data->array('order')); // array(2) { ["comment"]=> string(2) "Hi" ["quantity"]=> string(1) "3" }
Lenient Typed Data
Values with the requested type are valid, and strings which exactly represent an int, float or bool. Strings are validated, never cast.
use Tobento\Service\Collection\LenientTypedData; $data = new LenientTypedData([ 'page' => '2', 'amount' => '2.5', 'newsletter' => 'on', 'limit' => 'abc', ]); var_dump($data->int('page', 1)); // int(2) var_dump($data->float('amount')); // float(2.5) var_dump($data->bool('newsletter')); // bool(true) var_dump($data->int('limit', 20)); // int(20) ('abc' is not a valid int, so the default is returned)
Valid values
| Method | Valid values | Not valid (returns the default) |
|---|---|---|
int() |
int values: 3, -3strings: '3', '-3', '0' |
float values: 3.0strings: '03', '3.0', '1e3', ' 3', 'abc', overflow |
float() |
float values: 2.5int values: 3 (returned as 3.0)strings: '2.5', '-2.5', '3', '.5' |
strings: '2,5', '1e3', ' 2.5', '', 'abc' |
bool() |
bool values: true, falsestrings true: '1', 'true', 'on', 'yes'strings false: '0', 'false', 'off', 'no', '' |
int values: 1, 0strings: '2', ' 1', 'abc' |
Strings with leading or trailing whitespace are never valid. The string(), array() and list() methods work the same as in Strict Typed Data.
Translations
Create a new Translations collection.
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'title de', 'desc' => 'desc de', ], 'en' => [ 'title' => 'title en', 'desc' => 'desc en', ], 'fr' => [ 'title' => 'title fr', 'desc' => 'desc fr', ], ], 'en'); $trans->setLocaleFallbacks(['it' => 'en', 'de' => 'en']); // only get() method takes fallbacks into account. $trans->setLocaleMapping(['de-CH' => 'de', 1 => 'de']); // ['de-CH' (requested) => 'de' (stored)] $trans->setLocale('de'); // change default locale
setAll()
Set all translations. This would overwrite all previous translations.
use Tobento\Service\Collection\Translations; $trans = new Translations(); $trans->setAll([ 'de' => [ 'title' => 'title de', 'desc' => 'desc de', ], ]);
set()
Set translation(s) value by key.
use Tobento\Service\Collection\Translations; $trans = new Translations(locale: 'en'); $trans->setLocaleMapping(['de-CH' => 'de']); // set a title for the default locale. $trans->set('title', 'Title'); // set a title for specific locale. $trans->set('title', 'Title De', 'de'); // set a value for all current locales. $trans->set('foo', 'Bar', []); // set a value for specific locales. $trans->set('description', 'Description', ['de-CH', 'fr']); // using dot notation $trans->set('meta.color', 'red', []); /* Array ( [en] => Array ( [title] => Title [foo] => Bar [meta] => Array ( [color] => red ) ) [de] => Array ( [title] => Title De [foo] => Bar [description] => Description [meta] => Array ( [color] => red ) ) [fr] => Array ( [description] => Description [meta] => Array ( [color] => red ) ) ) */
add()
Add translation(s) value by key if it does not exist or is null.
use Tobento\Service\Collection\Translations; $trans = new Translations(locale: 'en'); $trans->setLocaleMapping(['de-CH' => 'de']); // add title for default locale if not exist or is null. $trans->add('title', 'Title'); // add title for specific locale if not exist or is null. $trans->add('title', 'Title De', 'de'); // add a value for all current locales if not exist or is null. $trans->add('foo', 'Bar', []); // add a value for specific locales if not exist or is null. $trans->add('description', 'Description', ['de-CH', 'fr']); // using dot notation $trans->add('meta.color', 'red', []);
get()
Get value for default locale:
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', 'meta' => [ 'color' => 'blue', ], ], ], 'en'); // get title for default locale. var_dump($trans->get('title')); // string(8) "Title en" // with a fallback value. var_dump($trans->get('foo', 'default')); // string(7) "default" // using dot notation. var_dump($trans->get('meta.color')); // string(4) "blue"
Get value for specific locale:
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', ], ], 'en'); $trans->setLocaleMapping(['de-CH' => 'de']); $trans->setLocaleFallbacks(['fr' => 'en']); // get title for specific locale. var_dump($trans->get('title', null, 'de-CH')); // string(8) "Title de" var_dump($trans->get('desc', null, 'de')); // NULL var_dump($trans->get('desc', 'Desc De', 'de')); // string(7) "Desc De" // As locale fallback is defined for fr and value fallback is null. var_dump($trans->get('title', null, 'fr')); // string(8) "Title en" // Does not fallback to en as fallback value is defined. var_dump($trans->get('title', 'Title Fr', 'fr')); // string(8) "Title Fr"
Get values for current locales:
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', ], ], 'en'); $titles = $trans->get('title', null, []); /*Array ( [de] => Title de [en] => Title en )*/
Get values for specific locales:
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', ], ], 'en'); $trans->setLocaleMapping(['de-CH' => 'de']); // fr locale fallsback to the default locale en // if no specific locale fallback is defined and // fallback value is null. $titles = $trans->get('title', null, ['de-CH', 'fr']); /*Array ( [de-CH] => Title de [fr] => Title en )*/ $trans->setLocaleFallbacks(['fr' => 'de']); // with fallback value ignoring any locale fallbacks $titles = $trans->get('title', 'Fallback value', ['de', 'fr']); /*Array ( [de] => Title de [fr] => Fallback value )*/ // without fallback value uses the above locale fallbacks. $titles = $trans->get('title', null, ['de', 'fr']); /*Array ( [de] => Title de [fr] => Title de )*/
has()
If translation(s) by key exist.
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', 'meta' => [ 'color' => 'blue', ], ], ], 'en'); $trans->setLocaleMapping(['de-CH' => 'de']); // uses default locale. var_dump($trans->has('title')); // bool(true) // with specific locale. var_dump($trans->has('title', 'de')); // bool(true) // uses all current locales. var_dump($trans->has('title', [])); // bool(true) // with specific locales var_dump($trans->has('title', ['de-CH', 'en'])); // bool(true) // using dot notation. var_dump($trans->has('meta.color', ['en'])); // bool(true)
all()
Get all translation(s).
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', ], ], 'en'); $trans->setLocaleMapping(['de-CH' => 'de']); // get all, default locale. $all = $trans->all(); // get all, specific locale. $all = $trans->all('de-CH'); // get all, all current locales. $all = $trans->all([]); // get all, specific locales. $all = $trans->all(['de-CH', 'fr']);
delete()
Delete translation(s) by key.
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', 'meta' => [ 'color' => 'blue', ], ], ], 'en'); $trans->setLocaleMapping(['de-CH' => 'de']); // delete default locale title. $trans->delete('title'); // delete specific locale title. $trans->delete('title', 'de-CH'); // delete all current locales titles. $trans->delete('title', []); // delete specific locales titles. $trans->delete('title', ['de-CH', 'fr']); // using dot notation $trans->delete('meta.color', []);
deleteAll()
Delete all translation(s).
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', ], ], 'en'); $trans->setLocaleMapping(['de-CH' => 'de']); // delete all, default locale. $trans->deleteAll(); // delete all, specific locale. $trans->deleteAll('de-CH'); // delete all, all current locales. $trans->deleteAll([]); // delete all, specific locales. $trans->deleteAll(['de-CH', 'fr']);
toArray()
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', ], ], 'en'); $array = $trans->toArray();
toJson()
use Tobento\Service\Collection\Translations; $trans = new Translations([ 'de' => [ 'title' => 'Title de', ], 'en' => [ 'title' => 'Title en', ], ], 'en'); $jsonString = $trans->toJson();
Credits
Idea and some code snippets is taken from Collections of the Laravel framework.