DEV Community

Cover image for PHP Map: Advanced collection methods
toco
toco

Posted on • Edited on • Originally published at aimeos.org

PHP Map: Advanced collection methods

The PHP Map package for collections contains many methods to ease your day to day work. They are extremely helpful in a lot of situations where you’ve written custom code up to now. This tutorial explains some of the most useful ones you don’t want to miss any more!

Installation

To use the PHP Map package, you have to add it to your composer based application:

composer req aimeos/map

Afterwards, there will be a map() function available, that creates a Map object which offers the methods explained.

col(): Map key/value pairs

There are numerous times when you need to transform an existing array into a list of key/value pairs. Think about a list of database records for example which should be indexed by ID instead of a sequencial index. The col() method creates that without the necessity of a loop:

$rows = [['id' => 1, 'code' => 'a'], ['id' => 3, 'code' => 'd']];
$result = map( $rows )->col( 'code', 'id' );

// Result equals:
map( [1 => 'a', 3 => 'd'] );
Enter fullscreen mode Exit fullscreen mode

This does also work with objects if they implement the magic PHP __get() method.

collapse() and flat(): Transform multi-dimensional arrays

Do you remember the last time when you had a multi-dimensional array and you needed a flat array of all elements? If you know the maximum depth, you can nest several loops but if not, you need to write a recursive function, which is very cumbersome. The Map object offers the flat() method, that can to do that in one line:

$array = [[1, 2], [3, [4, 5]]];
$result = map( $array )->flat();

// Result equals:
map( [1, 2, 3, 4, 5] );
Enter fullscreen mode Exit fullscreen mode

There’s a similar method named collapse(), that does almost the same but keeps the key of each value. Thus, it will overwrite elements with duplicate keys (also numeric keys):

$array = [['a' => 1, 'b' => 2], ['c' => 3, ['a' => 4, 'b' => 5]]];
$result = map( $array )->collapse();

// Result equals:
map( ['a' => 4, 'b' => 5, 'c' => 3] );
Enter fullscreen mode Exit fullscreen mode

Both methods accept the number of levels they should collapse or flatten as parameter. That way it’s easy to keep e.g. the third level and above unflattened or uncollapsed.

concat(), merge() and union(): Differences in combining arrays

In PHP, you can combine arrays using array_merge() or the + operator but their results are different. The PHP map package also offers the concat() method:

$one = [0 => 'a', 'b' => 2];
$two = ['b' => 3, 0 => 4];

$result = map( $one )->concat( $two );
//equals map( [0 => 'a', 'b' => 2, 1 => 3, 2 => 4] );

$result = map( $one )->merge( $two );
//equals map( [0 => 'a', 'b' => 3, 1 => 4] );

$result = map( $one )->union( $two );
//equals map( [0 => 'a', 'b' => 2] );
Enter fullscreen mode Exit fullscreen mode

concat() combines the elements of both arrays but doesn’t care about their keys and the resulting list will contain all elements of both arrays. merge() which uses the array_merge() function overwrites existing string keys but renumbers numeric keys. union() is the same as using the + operator and doesn’t add elements whose keys does already exist.

equals(): Test if arrays contains the same elements

To compare arrays, PHP only offers the == and === operators. They return only true if both array contains the same elements with the same keys (and the same order in case of ===). Contrary, the equals() method of the Map object can also test arrays without checking the keys:

$one = ['a', 'b'];
$two = ['b', 'a'];

map( $one )->equals( $two ); // true
map( $one )->equals( $two, true ); // false
Enter fullscreen mode Exit fullscreen mode

When passing true as second parameter, equals() works like the == operator.

find(): Return the first matching element

Sometimes, you need the first element from an array that matches your needs. The Map object offers the find() method which allows you to pass a closure function as parameter. This anonymous function can check whose element matches your needs:

map( ['a', 'c', 'e'] )->find( function( $value, $key ) {
    return $value >= 'b';
} );

// Result equals 'c'
Enter fullscreen mode Exit fullscreen mode

If no matching value is found, null is returned. If you pass true as second parameter, the search for the matching element will be reversed and you will get the last matching element.

first(), last(), get() and pull(): Advanced ways to retrieve an element

Fetching the first, last or any element of a list of elements is very handy and most useful in many cases. But the PHP Map object offers more than that. What if those methods don’t return null if no element is found but throw an exception or execute a closure? Instead of:

if( ( $item = map( [] ) )->first() === null ) {
    throw new \Exception( 'error' );
}

if( ( $item = map( [] ) )->last() === null ) {
    // fetch and return record from database
}
$id = $item->getId();
Enter fullscreen mode Exit fullscreen mode

you can simply write:

$id = map( [] )->first( new \Exception( 'error' ) )->getId();

$id = map( [] )->last( function() {
    // fetch and return record from database
} )->getId();
Enter fullscreen mode Exit fullscreen mode

All methods (first(), last(), get() and pull()) methods accept either an exception that’s thrown or a closure that’s executed if no element is available. Thus, it saves you the annoying checks that make you code full of if/else statements.

If you pass an exception, the exception object is created whether it’s used or not. This is no problem as long as you don’t do that in a loop. Then, create one exception object before the loop and pass only the variable to the methods.

in(): Check if several elements are in the list

To test if an value is in a list of elements with in() isn’t much different than using in_array(). But what if you need to do that several times with different elements? Then, in() takes away the burden of doing that within a loop:

map( ['a', 'b', 'c'] )->in( 'b' ); // true
map( ['a', 'b', 'c'] )->in( ['b', 'a'] ); // true
map( ['a', 'b', 'c'] )->in( ['d', 'a'] ); // false
Enter fullscreen mode Exit fullscreen mode

The in() method of the Map object checks if all elements of the passed array are available in the list and only then it returns true.

toJson(): Encode everything into JSON

When working with APIs, you almost always need to encode the result into its JSON representation. The PHP map object offers a convenient way to transform all elements to JSON:

map( ['a', 'b', ['c', 'd'] )->toJson();
Enter fullscreen mode Exit fullscreen mode

Like the PHP json_encode() method, it accepts a parameter for passing options that are used during encoding the elements. There are two that you may find extremely useful:

map( ['a', 'b', ['c', 'd'] )->toJson( JSON_FORCE_OBJECT|JSON_HEX_QUOT );
Enter fullscreen mode Exit fullscreen mode

JSON_FORCE_OBJECT always creates {}, even for empty lists to avoid being encoded as []. If you add JSON output to HTML data attributes for storing something you need for dynamic Javascript features later on, JSON_HEX_QUOT saves you from generating invalid HTML.

walk(): Visit each value in a multi-dimensional array

You receive a multi-dimensional array from an HTML form and want to make sure that all values are of a certain type or doesn’t contain HTML tags? Then, the walk() method of the PHP Map object is your friend:

map( ['a', 'b', ['c', '<b>d</b>'] )->walk( function( &$val ) {
    $val = strip_tags( $val );
} );
Enter fullscreen mode Exit fullscreen mode

You can also create a mapping based on the list you pass as second parameter:

$mapping = [1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'];

map( [1, 2, [3, 4] )->walk( function( &$val, $key, $data ) {
    $val = $data[$val] ?? $val;
}, $mapping );

// Result equals
map( ['one', 'two', ['three', 'four']] )
Enter fullscreen mode Exit fullscreen mode

method(): Register your own methods

The best of all comes last: You can extend the PHP Map object by your own methods! This is extremely handy if you need the same code several times and different places. To register your own method, just do this:

\Aimeos\Map::method( 'strrev', function( $sep ) {
    return strrev( join( '-', $this->list ) );
} );
Enter fullscreen mode Exit fullscreen mode

Your closure function has access to the internal array of elements using $this->list and you can read and/or modify that internal array of the Map object. You can use your new method the same way as any other method of the Map object afterwards:

map( ['a', 'b'] )->strrev( '-' );
// returns "b-a"
Enter fullscreen mode Exit fullscreen mode

Conclusion

The PHP Map object offers some great methods that can simplify your code drastically and you will never want to miss them any more. Especially the possibility to register own methods to extend the Map object dynamically gives your great power in a few lines of code.

Top comments (0)