Giter Club home page Giter Club logo

chain's Introduction

Chain

Chain provides you with a consistent and chainable way to work with arrays in PHP.

Build Status Windows Build status Scrutinizer Code Quality Code Coverage

Made by Florian Eckerstorfer in Vienna, Europe.

Motivation

Let us be honest. Working with arrays in PHP is a mess. First of all, you have to prefix most (but not all) functions with array_, the parameter ordering is not consistent. For example, array_map() expects the callback as first parameter and the array as the second, but array_filter() expects the array as first and the callback as second. You also have to wrap the function calls around the array, the resulting code is not nice to look at, not readable and hard to understand.

$arr = array_filter(
    array_map(
        function ($v) { return rand(0, $v); },
        array_fill(0, 10, 20)
    ),
    function ($v) { return $v & 1; }
);

Chain wraps the array in an object and gives you a chainable interface.

$chain = Chain::fill(0, 10, 20)
    ->map(function ($v) { return rand(0, $v); })
    ->filter(function ($v) { return $v & 1; });

Take a look at the following code. How long do you need to understand it?

echo array_sum(array_intersect(
    array_diff([1, 2, 3, 4, 5], [0, 1, 9]),
    array_filter([2, 3, 4], function ($v) { return !($v & 1); })
));

What about this?

echo Chain::create([1, 2, 3, 4, 5])
    ->diff([0, 1, 9])
    ->intersect(Chain::create([2, 3, 4])->filter(function ($v) { return !($v & 1); }))
    ->sum();

Hint: It takes the diff of two arrays, intersects it with a filtered array and sums it up.

Installation

You can install Chain using Composer:

$ composer require cocur/chain

Usage

Chain allows you to create, manipulate and access arrays.

Array Creation

You can create a Chain by passing an array to the constructor.

$chain = new Chain([1, 2, 3]);

Or with a convenient static method:

$chain = Chain::create([1, 2, 3]);

In addition a Chain can also be created by the static fill() method, which is a wrapper for the array_fill() function.

$chain = Chain::fill(0, 10, 'foo');

There is also a method, ::createFromString(), that creates the Chain from a string. In addition to the string you need to provide a delimiter, which is used to split the string. If the option regexp is passed in the delimiter must be a regular expression.

Chain::createFromString(',', '1,2,3')->array;                               // -> [1, 2, 3]
Chain::createFromString('/,|.|;/', '1,2.3;4,5', ['regexp' => true])->array; // -> [1, 2, 3, 4, 5]

Array Manipulation

Chains manipulation methods manipulate the underlying array and return the object, that is, they can be chained. Most of the methods are simple wrappers around the corresponding array_ function.

In the following example ->map() is used to multiply each element by 3 and then filter the array to only contain odd elements.

$chain = (new Chain([1, 2, 3]))
    ->map(function ($v) { return $v*3; })
    ->filter(function ($v) { return $v & 1; });
$chain->array; // -> [3, 9]

When a method accepts an array (->diff() or ->intersect()) you can also pass in another instance of Chain instead of the array.

List of Array Manipulation Methods

All of these methods manipulate the array, but not all of them return an instance of Cocur\Chain\Chain. For example, ->shift() removes the first element from the array and returns it.

  • ->changeKeyCase()
  • ->combine(array|Chain, array|Chain)
  • ->count()
  • ->diff(array|Chain)
  • ->filter(callable)
  • ->find(callable)
  • ->flatMap(callable)
  • ->flip()
  • ->intersect(array|Chain)
  • ->intersectAssoc(array|Chain)
  • ->intersectKey(array|Chain)
  • ->keys()
  • ->map(callable)
  • ->merge(array|Chain)
  • ->pad(int, mixed)
  • ->pop()
  • ->product()
  • ->push(mixed)
  • ->reduce(callable[, int])
  • ->reverse([bool])
  • ->search(mixed[, bool])
  • ->shift()
  • ->shuffle()
  • ->sort(sortFlags)
  • ->sortKeys(sortFlags)
  • ->splice(offset[, length[, replacement]])
  • ->sum()
  • ->unique()
  • ->unshift(mixed)
  • ->values()

Array Access

Most importantly you can access the underlying array using the public array property.

$chain = new Chain([1, 2, 3]);
$chain->array; // -> [1, 2, 3]

Chain implements the Traversable interface.

$chain = new Chain([1, 2, 3]);
foreach ($chain as $key => $value) {
  // ...
}

It also implements the ArrayAccess interface allowing to access elements just like in any normal array.

$chain = new Chain();
$chain['foo'] = 'bar';
if (isset($chain['foo'])) {
    echo $chain['foo'];
    unset($chain['foo']);
}

Additionally Chain contains a number of methods to access properties of the array. In contrast to the manipulation methods these methods return a value instead of a reference to the Chain object. That is, array access methods are not chainable.

$chain = new Chain([1, 2, 3]);
$chain->count(); // -> 3
$chain->sum();   // -> 6
$chain->first(); // -> 1
$chain->last();  // -> 3

$chain->reduce(function ($current, $value) {
    return $current * $value;
}, 1); // -> 6

List of Array Access Methods

  • ->count()
  • ->countValues()
  • ->every(callable)
  • ->first()
  • ->includes(mixed[, array])
  • ->join([$glue])
  • ->last()
  • ->reduce()
  • ->some()
  • ->sum()

Author

Chain has been developed by Florian Eckerstorfer (Twitter) in Vienna, Europe.

Chain is a project of Cocur. You can contact us on Twitter: @cocurco

Support

If you need support you can ask on Twitter (well, only if your question is short) or you can join our chat on Gitter.

Gitter

In case you want to support the development of Chain you can send me an Euro or two.

Change Log

Version 0.9.0 (19 July 2020)

Version 0.8.0 (12 September 2019)

  • #40 Update tooling and dependencies, set minimum PHP version to 7.2
  • #37 Add missing traits \Cocur\Chain\Chain (by nreynis)
  • #41 Add ➞ flatMap() (by nreynis)

Version 0.7.0 (11 November 2018)

Version 0.6.0 (5 April 2018)

Version 0.5.0 (4 December 2017)

Version 0.4.1 (4 December 2017)

  • Add Find link to Cocur\Chain\Chain

Version 0.4.0 (22 September 2017)

Version 0.3.0 (7 September 2017)

  • #12 Restore constructor (by sanmai)
  • Move first() and last() methods into traits
  • Add additional links (CountValues, KeyExists, Splice)
  • Update Merge link

Version 0.2.0 (6 November 2015)

  • #11 Add Cocur\Chain\Chain::createFromString() to create a chain from a string
  • #10 Add methods to Cocur\Chain\AbstractChain to retrieve first and last element of chain.
  • #9 Cocur\Chain\Chain is now countable

Version 0.1.0 (6 November 2015)

  • Initial release

License

The MIT license applies to Chain. For the full copyright and license information, please view the LICENSE file distributed with this source code.

chain's People

Contributors

arpple avatar davidmz avatar florianeckerstorfer avatar gries avatar nreynis avatar peter279k avatar sanmai avatar silvadanilo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

chain's Issues

values method is undefined

The Values Trait is never imported in Chain, trying to use it will spawn a Fatal error: undefined method.

Fill is a static factory function it should be moved to Chain

Fill is actually a Trait.
https://github.com/cocur/chain/blob/master/src/Link/Fill.php

This is the only Trait that don't operate on an instance and also the only one that is static.
Other Traits are all instance methods, this one is a factory.

This code should be moved to the Chain class which host other factory methods create and createFormString. This would be an entirely transparent change, it would make the code more consistent and would prevent statistical analysis failing:

https://scrutinizer-ci.com/g/cocur/chain/inspections/fba87ae0-dc03-4190-966a-64fd5f908b9b/issues/files/src/Link/Fill.php?status=all&orderField=path&order=asc&honorSelectedPaths=0

Tooling needs an update, automated testing fails.

Tooling is out of date and cause tests to fail:

  • travis for HHVM fails because composer dropped support:
    composer/composer#8127
    You should probably drop it too.
  • travis for nightly fails because there´s currently no stable release of phpUnit supporting PHP8
    IMHO you should temporarly disable nightly and update to current phpUnit release
  • appveyor fails because php version has been bumped from 7.2 to 7.3

If you want I can take some time to provide a PR.

Filter turns regular (lists) arrays to associative (hashmaps) array

When you filter an array, it may become 'sparse'. PHP deals with this situation by turning it to an associative array:
https://eval.in/916518

<?php
$a1 = ['a', 'z', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'q', 's', 'd'];
$a2 = ['a', 'z', 'e', 'r', 't', 'y', 'u', 'i', 'o'];

echo json_encode(array_filter($a1, function($i){ return $i != 's'; }));
echo json_encode(array_filter($a2, function($i){ return $i != 's'; }));
{"0":"a","1":"z","2":"e","3":"r","4":"t","5":"y","6":"u","7":"i","8":"o","9":"p","10":"q","12":"d"}
["a","z","e","r","t","y","u","i","o"]

This is really disturbing and unintuitive, no other language (AFAIK) do that. I know PHP always had this permanent confusion between lists and maps, but this doesn't feel right: if I filter a list I should get a filtered list in return.

Would you consider adding a boolean argument forceList or adding a filterList method who always return array_values in the internal state ?

Add join / implode Link

It would be great if a join or implode method could be added. The Link would work like implode($glue, $array).

$array = [1, 2, 3];

Chain::create($array)->join('&'); // "1&2&3"

What is missing ? should be implemented next ?

I made a matrix comparing JS array API, PHP anc Chain.
I've matched the methods when possible.
I haven't dig into JS objects API, nor lodash or reactivex yet.

  JS PHP Chain comments
✔️ length count count  
copyWithin      
✔️ fill array_fill fill  
  array_fill_keys    
✔️   array_flip flip  
✔️ pop array_pop pop  
✔️ push array_push push  
✔️ reverse array_reverse reverse  
✔️ shift array_shift shift  
✔️ splice array_splice splice  
✔️ unshift array_unshift unshift  
✔️ concat array_merge merge  
✔️   array_merge_recursive merge (with option recursive = true)  
✔️ includes in_array   implemented
✔️ indexOf array_search search  
lastIndexOf      
✔️ join implode join  
✔️ slice array_slice slice  
entries      
✔️ every     implemented
✔️ filter array_filter filter  
✔️ find   find  
findIndex      
forEach array_walk   TODO: implement, what would be a good name? walk, or tap (used in reactivex, also used in lodash but with different semantic) or peek (used in java streams API)
  array_walk_recursive   TODO: implement as an option
✔️ keys array_keys keys  
flat     TODO: implement
✔️ flatMap   flatMap  
✔️ map   map  
  array_map   PHP implementation is… let's just say it's weird. Better not reuse that.
✔️ reduce array_reduce reduce  
reduceRight     TODO: implement, as an option of reduce maybe ?
✔️ some     implemented
  range   TODO: implement
✔️ values array_values values  
✔️   array_combine combine  
✔️   array_key_exists keyExists  
✔️   array_pad pad  
✔️   array_product product  
✔️   array_rand rand  
✔️   array_sum sum  
✔️   shuffle shuffle  
✔️   array_unique unique  
✔️   reset first  
✔️   end last  
  array_key_first   TODO: implement for orthogonality, we already have the methods for the values
  array_key_last   TODO: implement for orthogonality, we already have the methods for the values
✔️   array_replace replace  
  array_replace_recursive   should replace have a recursive option, like merge ?
✔️   array_change_key_case changeKeyCase  
  array_chunk   TODO: implement
  array_column   TODO: implement, I think pluck (used in underscore, lodash, data tables, …) would be a better name
✔️   array_count_values countValues  
✔️   uasort sort (with callable parameter and option assoc = true)  
✔️ sort usort sort (with callable parameter)  
✔️   arsort sort (with option assoc = true and reverse = true)  
✔️   asort sort (with option assoc = true)  
✔️   rsort sort (with option reverse = true)  
✔️ sort sort sort  
✔️   uksort sortKeys (with callable parameter)  
✔️   krsort sortKeys (with option reverse = true)  
✔️   ksort sortKeys  
✔️   array_diff diff  
  array_diff_assoc   shouldn't diff have a bunch of options like sort ?
  array_diff_key   shouldn't diff have a bunch of options like sort ?
  array_diff_uassoc   shouldn't diff have a bunch of options like sort ?
  array_diff_ukey   shouldn't diff have a bunch of options like sort ?
  array_udiff   shouldn't diff have a bunch of options like sort ?
  array_udiff_assoc   shouldn't diff have a bunch of options like sort ?
  array_udiff_uassoc   shouldn't diff have a bunch of options like sort ?
✔️   array_intersect intersect  
⁉️   array_intersect_assoc intersectAssoc shouldn't intersect have a bunch of options like sort ? Should this be deprecated in favor of intersect with option assoc = true for consistency ?
  array_intersect_uassoc   shouldn't intersect have a bunch of options like sort ?
✔️   array_intersect_key intersectKey  
  array_intersect_ukey   shouldn't intersectKey have a bunch of options like sort ?
  array_uintersect   shouldn't that be in one of the existing intersect operators ?
  array_uintersect_assoc   shouldn't that be in one of the existing intersect operators ?
  array_uintersect_uassoc   shouldn't that be in one of the existing intersect operators ?
  natsort    
  natcasesort    
  array_multisort    
  compact   do not really see use case seems like a legacy method, prefer merge, unshift or push

What I think needs to be implemented:

  • includes (in_array)
  • every
  • flat
  • some
  • range
  • chunk
  • pluck
  • firstKey
  • lastKey
  • walk / tap / peek, not sure about the name

I also noticed that diff and intersect needs some work, some modes aren't supported. Also it would be nice to keep consistency with what has been done in sort.

What's your opinion about that ?

Deprecation warnings in PHP 8.1

PHP 8.1 requires the offsetGet function to be compatible with the ArrayAccess declaration, i.e. to have a mixed return type. Without this, the Chain library produces deprecation notices like this:

PHP Deprecated:  Return type of Cocur\Chain\AbstractChain::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in .../vendor/cocur/chain/src/AbstractChain.php on line 46

Mixed type is only available in PHP 8, Chain supports 7.2, so the only way to meet the new requirements is to add the #[\ReturnTypeWillChange] attribute.

Callable on map idiosyncratic

map()s callable receives two arguments, the value and its index. This makes using standard library functions like trim and intval problematic without wrapping them in an anonymous or arrow function (because those functions have a second argument that alters behaviour).

It would be useful to have an alternative map() that does not supply the index. Without breaking the API this would need to be something like mapValue(), whereas it might have been preferable to have map() and mapWithIndex().

In part, the lack of documentation for each of Chain's methods, in regards to the Callable's parameters, doesn't help, without digging into the source.

Implement Chain::merge(...$arrays)

I have a scenario where i need to merge multiple domain value objects and it would be handy if one could just do:

Chain::merge($object->getOutsidePhotos(), $object->getInsidePhotos())
    ->map(fn (Photo $photo) => $photo->getUrl())
    ->map(fn (string $url) => $this....);

compared to:

Chain::create(array_merge($object->getOutsidePhotos(), $object->getInsidePhotos()))
    ->map(fn (Photo $photo) => $photo->getUrl())
    ->map(fn (string $url) => $this....);

Also different function of merge can be implemented, for example according to Optimizing array merge operation you will get better speed if you do not use the array_merge function in large arrays, because of some conditions php check internally.

What do you think?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.