📜 ⬆️ ⬇️

Colada - convenient work with collections

Colada is a library for convenient and safe work with collections in PHP.

This is, above all, the work in object-oriented style (which out of the box in PHP is rather inconvenient). Non-modifiable collections, NPE protection (Null Pointer Exception) with optional values, any values ​​(not just scalars) for keys in maps — that's all about it.

Installation


Installing the library through Composer is as easy as ... As well as connecting it as a git submodule :)
')
{ "require": { "alexeyshockov/colada": "dev-master" } } 

 $ git submodule add git://github.com/alexeyshockov/colada.git vendor/colada 

Collections


How to use Colada after installation? There are several options, the simplest of which is to make a collection from a regular array, which in most cases is already available, and with which you need to make some modifications:

 $counts = to_collection(array(2, 3, 50, 36)); $users = to_set(array('label1', 'label2', 'label3')); 

You can also create a collection yourself, if you have all the necessary elements:

 $counts = collection(2, 3, 50, 36); $users = set('label1', 'label2', 'label3'); 

Almost like a standard array ()!

mapBy (), acceptBy () / rejectBy (), foldBy ()


A good description of the functional style of working with collections is already pretty well given in the article “ What's wrong with for loops? ". In the Colada map, accept / reject and fold have exactly the same meaning as in all other libraries for working with collections (Underscore.js, Scala Collections, etc.).

Consider an example:

 $saleCount = $users ->acceptBy(x()->isActive()) ->mapBy(x()->getSales()) ->foldBy(function($count, $posts) { return $count + count($posts); }, 0); 


I hope the code above speaks for itself, and its meaning is clear in the course of reading, except ... What is x ()?

x ()


For those who have programming experience on Scala or a similar language in which working with closures was initially made as convenient as possible, this may seem familiar.

Scala:

 val emails = users.map(x.getEmail()); 

PHP (Colada):

 $emails = $users->mapBy(x()->getEmail()); 

In PHP, working with closures is somewhat more verbose than in the same Scala. But for most cases, when you just need to call getter on an object in the collection, everything can be simplified, as shown above.

x () is the future element of the collection. Using it is equivalent to simply closing:

 $emails = $users->mapBy(function($user) { return $user->getEmail() }); 

Are you still writing a closure for every little thing? Then we go to you! ;)

Heshi (aka maps)


If collections are simply sets of elements, then hashes are key / value pairs, which we also encounter every day. As with regular collections, we can create a hash

 $users = to_map(array('managers' => array(1, 3), 'users' => array(2, 3, 4))); 

and work with it in object-oriented style using familiar mapBy () and acceptBy () / rejectBy (), as well as other useful and convenient methods: mapElementsBy (), flip (), pick ().

Someone said that a code listing is sometimes better than a thousand words:

 $usersByGroups = $users ->mapBy(x()->isActive()) ->groupBy(x()->getGroup()) ->mapElementsBy(x()->count()); foreach ($groups as $group) { echo 'Group "'$group->getName().'" contains '.$usersByGroups->get($group)->orElse(0).' users.'; echo "\n"; } 

In the code above, you can notice one feature: apart from the “sugar” methods that simplify working with collections and hashes, Map :: get () does not return the value by key, but the wrapper object of some Option class, which I did not mention before current moment.

Optional values


Sir Anthony Hoar, who is credited with introducing NULL values, once said:
I call it my billion-dollar mistake.

On NPE (Null Pointer Exception), many words were said and several solutions were invented. One of them is the introduction of so-called optional values. In some languages ​​they are originally: in Haskell it is Maybe, in Scala - Option. For other languages ​​there are libraries, such as Optional from Google Guava for Java.

The optional values ​​in the Colada are very similar to their Scala counterpart. Some means having a value, None - its absence. In use, the optional values ​​are very similar to collections (to simplify, you can think that a collection of one or zero elements is returned):

 echo ": "; echo $user->getCity()->mapBy(x()->getName())->orElse('-'); 

The example above shows how the optional value can be applied in the domain model classes. In this case, when working with the user's city, we will never forget that it may not be full. Similarly, we will never forget that the requested key may not exist at all in a given hash.

Make me unsee it
If you are sure that the key is in the hash, or you don’t want to bind with optional values ​​for some other reason, you can always use the apply () method, which, unlike get (), will return the element without any wrappers:

 foreach ($groups as $group) { echo 'Group "'$group->getName().'" contains '.$usersByGroups->apply($group).' users.'; echo "\n"; } 

And catch an exception if there is no element.

The use of optional values ​​is a rather extensive topic, the detailed description of which is a separate article.

Performance and memory usage


High performance and optimal use of memory were not the main goals from the beginning - usability was put at the forefront. In the overwhelming majority of cases, work happens with small collections, so the difference in performance will not be noticeable.

Nevertheless, even in this situation, the result turned out quite to myself, in my opinion. Due to the use of SplFixedArray for the implementation of collections, the memory usage is even less compared to regular arrays:

Memory usage by the example of a collection of 100,000 items
 $ ./shell.sh // Call use_colada() function to benefit from all shortcuts ;) Interactive shell php > use_colada(); php > vd(memory_get_usage(), memory_get_peak_usage()); int(374736) int(383040) php > $nums = Collections::range(1, 100000); php > vd(memory_get_usage(), memory_get_peak_usage()); int(3575236) int(3581604) php > $nums = range(1, 100000); php > vd(memory_get_usage(), memory_get_peak_usage()); int(8099280) int(8105996) 

Regarding the speed of work, there are no miracles - you have to pay for ease of use ... Advanced comparison of elements when searching (Collection :: contains (), Map :: get () ...), as well as the rest of the buns are implemented in the forehead and, of course, work slower than embedded analogs. But things can change for the better, including with your help;)

But even here there is something that is already optimized, and what you need to remember and not be afraid to use - the transformation chains from mapBy () and acceptBy () / rejectBy () are lazy by default! This is very familiar to people who have had programming experience in functional languages, and is clearly seen in the example:

 $emails = $users ->acceptBy(x()->isActive()) ->mapBy(x()->getEmail()); 

In this code, the passage through the user’s collection will be only one, not two, as one might think. More precisely, in the code above it will not be at all. The pass will be executed only when the elements of the new collection are really needed:

 $emails = $users ->acceptBy(x()->isActive()) ->mapBy(x()->getEmail()); foreach ($emails as $email) { echo $email; } 

"Competitors"


Inspired by the Scala language (Scala Collections) and the Underscore.js library, from the very beginning I could not imagine that there was no such thing for PHP. But either I was looking badly, or indeed at the time of development there were no solutions providing the same convenience in PHP.

Nevertheless, there are some developments and I would like to give them here so that everyone can make a comparison and choose the appropriate tool.

So, what is already on the Internet:

In custody


Useful (possible) links:

PS


At first glance, all this may seem like nonsense, and the code written certainly not in PHP. If someone is still interested in it, I look forward to constructive comments, with the help of which I will be able to reveal those things that I have not described fully enough.

Source: https://habr.com/ru/post/151558/


All Articles