A redacted PHP port of Underscore.js with additional functions and goodies – Available for Composer and Laravel

Overview

Underscore.php

Build Status Latest Stable Version Total Downloads Scrutinizer Quality Score Code Coverage Support via Gittip

The PHP manipulation toolbelt

First off : Underscore.php is not a PHP port of Underscore.js (well ok I mean it was at first). It's doesn't aim to blatantly port its methods, but more port its philosophy.

It's a full-on PHP manipulation toolbet sugar-coated by an elegant syntax directly inspired by the Laravel framework. Out through the window went the infamous __(), replaced by methods and class names that are meant to be read like sentences à la Rails : Arrays::from($article)->sortBy('author')->toJSON().

It features a good hundred of methods for all kinds of types : strings, objects, arrays, functions, integers, etc., and provides a parsing class that help switching from one type to the other mid-course. Oh also it's growing all the time. The cherry on top ? It wraps nicely around native PHP functions meaning Strings::replace is actually a dynamic call to str_replace but with the benefit of allowed chaining and a finally consistant argument order (all functions in Underscore put the subject as the first argument, NO MATTER WHAT).

It works both as a stand-alone via Composer or as a bundle for the Laravel framework. So you know, you don't really have any excuse.

Install Underscore

To install Underscore.php simply run composer require anahkiasen/underscore-php. Note that Underscore's type classes (Arrays/Strings/etc) are by default namespaced in the Types folder, so to use Arrays, you would do the following :

use Underscore\Types\Arrays;

Using Underscore

It can be used both as a static class, and an Object-Oriented class, so both the following are valid :

$array = array(1, 2, 3);

// One-off calls to helpers
Arrays::each($array, function($value) { return $value * $value; }) // Square the array
Function::once($myFunction) // Only allow the function to be called once
Number::paddingLeft(5, 5) // Returns '00005'
Object::methods($myObject) // Return the object's methods
Strings::length('foobar') // Returns 6

// Or chain calls with the 'from' method
Arrays::from($array)->filter(...)->sort(...)->get(2)

// Which does this in the background
$array = new Arrays($array);
$array->filter(...)->sort(...)->get(2)

For those nostalgic of ye old __() a generic Underscore class is provided that is able to go and fetch methods in all of Underscore.php's methods. For this it looks into the methods it knows and analyzes the subject of the method (meaning if you do Underscore::contains('foobar', 'foo') it knows you're not looking for Arrays::contains).

On types : it's important to note that using a specific type class to create an Underscore repository will convert the type of provided subject. Say you have an object and do new Arrays($myObject) – this will convert the object to an array and allow you to use Arrays methods on it. For this Underscore uses its Parse class's methods that for the most part just type cast and return (like this (array) $object) but it also sometimes go the extra mile to understand what you want to do : if you convert an array to a string, it will transform it to JSON, if you transform an array into an integer, it returns the size of the array, etc.

The core concept is this : static calls return values from their methods, while chained calls update the value of the object they're working on. Which means that an Underscore object don't return its value until you call the ->obtain method on it — until then you can chain as long as you want, it will remain an object. The exception are certains properties that are considered breakers and that will return the object's value. An example is Arrays->get.

Note that since all data passed to Underscore is transformed into an object, you can do this sort of things, plus the power of chained methods, it all makes the manipulation of data a breeze.

$array = new Arrays(['foo' => 'bar']);

echo $array->foo // Returns 'bar'

$array->bis = 'ter'
$array->obtain() // Returns array('foo' => 'bar', 'bis' => 'ter')

Customizing Underscore

Underscore.php provides the ability to extend any class with custom functions so go crazy. Don't forget that if you think you have a function anybody could enjoy, do a pull request, let everyone enjoy it !

Strings::extend('summary', function($string) {
  return Strings::limitWords($string, 10, '... — click to read more');
});

Strings::from($article->content)->summary()->title()

You can also give custom aliases to all of Underscore's methods, in the provided config file. Just add entries to the aliases option, the key being the alias, and the value being the method to point to.

Extendability

Underscore.php's classes are extendable as well in an OOP sense. You can update an Underscore repository with the setSubject method (or directly via $this->subject = granted you return $this at the end). When creating an Underscore repository, by default it's subject is an empty string, you can change that by returning whatever you want in the getDefault method.

class Users extends Arrays
{
  public function getDefault()
  {
    return 'foobar';
  }

  public function getUsers()
  {
    // Fetch data from anywhere

    return $this->setSubject($myArrayOfUsers);
  }
}

$users = new Users; // Users holds 'foobar'
$users->getUsers()->sort('name')->clean()->toCSV()

// Same as above
Users::create()->getUsers()->sort('name')->clean()->toCSV()

It is important to not panic about the mass of methods present in Underscore and the dangers extending one of the Types would cause : the methods aren't contained in the classes themselves but in methods repositories. So if you extend the Strings class and want to have a length method on your class that has a completely different meaning than Strings::length, it won't cause any signature conflict or anything.

Also note that Underscore method router is dynamic so if your subject is an array and mid course becomes a string, Underscore will always find the right class to call, no matter what you extended in the first place. Try to keep track of your subject though : if your subject becomes a string, calling per example ->map will return an error.

Call to native methods

Underscore natively extends PHP, so it can automatically reference original PHP functions when the context matches. Now, PHP by itself doesn't have a lot of conventions so Arrays:: look for array_ functions, Strings:: look for str_ plus a handful of other hardcoded redirect, but that's it. The advantage is obviously that it allows chaining on a lot of otherwise one-off functions or that only work by reference.

Arrays::diff($array, $array2, $array3) // Calls `array_diff`
Arrays::from($array)->diff($array2, $array3)->merge($array4) // Calls `array_diff` then `array_merge` on the result

Documentation

You can find a detailed summary of all classes and methods in the repo's wiki or the official page. The changelog is available in the CHANGELOG file.

About Underscore.php

There is technically another port of Underscore.js to PHP available on Github — I first discovered it when I saw it was for a time used on Laravel 4. I quickly grew disapoint of what a mess the code was, the lack of updates, and the 1:1 mentality that went behind it.

This revamped Underscore.php doesn't aim to be a direct port of Underscore.js. It sometimes omits methods that aren't relevant to PHP developers, rename others to match terms that are more common to them, provides a richer syntax, adds a whole lot of methods, and leaves room for future ones to be added all the time — whereas the previous port quickly recoded all JS methods to PHP and left it at that.

If you come from Javascript and are confused by some of the changes, don't put all the blame on me for trying to mess everything up. A basic example is the map function : in PHP it has a completely different sense because there exists an array_map function that basically does what __::invoke does in JS. So map is now Arrays::each. Always keep in mind this was made for PHP developpers first, and differences do exist between the two to accomdate the common terms in PHP.

Comments
  • Helper function conflicts with existing translation function

    Helper function conflicts with existing translation function

    I'm developing a Laravel webapplication which hooks into Magento (don't ask me why..), which now throws Cannot redeclare __() since Magento tries to define this function as a translation helper.

    Any ideas on how to circumvent this? The undescore-php helper function is declared first, so a simple if( ! function_exists( '__' ) ) { ... } won't suffice.

    opened by mauvm 13
  • Collections: method names and arguments

    Collections: method names and arguments

    I used underscore.php for some time but it lacks support so I was very happy when I found your bundle.

    It is not verbatim version of JS original as you warn, but it has some changes not so obvious for me. So I have a few questions.

    1. Why did you remove map() method?
    2. Why you do not pass a collection as an argument to an iterator.
    3. And the last one is more of a wish: reduce is one of the most powerful methods in Underscore. Do you plan to add it anytime soon?

    The code of your library is nicely structured and written. Digging it itself was very valuable for me. Thank you for sharing it.

    opened by kapooostin 10
  • Array::invoke() fails when called with additional arguments

    Array::invoke() fails when called with additional arguments

    I guess there is a typo and an error:

    353: if ($arguments) return array_map($callable, $array, $callable);
    

    Did you mean to pass $arguments? Anyway it won't work—the third argument is not a list of arguments for a callable. It is another array to be processed with it. It is not a Laravel class :)

    It could be used to map arguments for each call of a callable, but it should have the same length as the main collection. Eg., if we want to trim underscore symbol we need to pass it as following:

    $array = array('foo______', '_____bar');
    $array = Arrays::invoke($array, 'trim', array('_','_'));
    $this->assertEquals(array('foo', 'bar'), $array);
    
    opened by kapooostin 7
  • without improvement

    without improvement

    I find the current signature of without very limiting (aka, passing in an unbounded list of exclusion parameters) so I've written it so that this can be preserved but you can instead send in just one ARRAY parameter to act as the exclusion list.

    That is probably not the clearest explanation but hopefully the code/unit test is clear enough.

    opened by yankeeinlondon 6
  • added findBy() and filterBy() methods

    added findBy() and filterBy() methods

    I've added two related functions which operate on Array or Object collections:

    1. filterBy - Allows you to filter down a collection to only those objects which have a matching name/value pair.

      usage:

       Object::filterBy($collection, $property, $value, $comparisonOperation = "eq");
       Arrays::filterBy($collection, $property, $value, $comparisonOperation = "eq");
      
    2. findBy - Very similar to filterBy but assumes that only one result should be returned and therefore unpacks the surrounding array.

      usage:

       Object::findBy($collection, $property, $value, $comparisonOperation = "eq");
       Arrays::findBy($collection, $property, $value, $comparisonOperation = "eq");
      

    note: If the filter operation actually does return more than one result, findBy will take the first object in the resulting array of objects.

    opened by yankeeinlondon 6
  • Php 7 - Fatal error: Cannot use Underscore\Types\String as String because 'String' is a special class name

    Php 7 - Fatal error: Cannot use Underscore\Types\String as String because 'String' is a special class name

    In php7 there is this error:

    Fatal error: Cannot use Underscore\Types\String as String because 'String' is a special class name in /myproject/vendor/anahkiasen/underscore-php/src/Methods/StringMethods.php on line 5
    

    Php7 not allows the classes to have a name like string, bool, int, ... See http://php.net/manual/en/reserved.other-reserved-words.php for more details

    opened by maarekj 4
  • Arrays::find returns original array if nothing found

    Arrays::find returns original array if nothing found

    Hi.

    I am not sure if this is the intention, but I would expect to get "null" if nothing can be found that satisfies the predicate.

    The code in ArraysMethods.php is:

      /**
       * Find the first item in an array that passes the truth test
       */
      public static function find($array, Closure $closure)
      {
        foreach ($array as $key => $value) {
          if ($closure($value, $key)) return $value;
        }
    
        return $array;
      }
    
    opened by mickeyvip 4
  • multi-array to assoc based on key

    multi-array to assoc based on key

    Not sure if I can already efficiently do this with the current functions but I often used the following to convert a regular array of associative arrays to an associative array of same arrays by a given key (my example should make more sense).

    I called it array hash map, probably not the best name though.

    function array_hash_map($key, $array) {
            if (!$array) {
                return [];
            }
            $ret = [];
            foreach ($array as $a) {
                $ret[$a[$key]] = $a;
            }
            return $ret;
        }
    

    Example:

    $a = [
     ['id' => 5, 'name': foo],
     ['id' => 10, 'name': bar]
    ];
    
    $a = array_hash_map('id', $a);
    
    /*
      $a is now: 
      [
        '5' => ['id' => 5, 'name': foo],
        '10' => ['id' => 10, 'name': bar]
      ]
    */
    

    Just a suggestion of a possible utility function for Arrays.

    opened by scragg0x 3
  • Implement indexBy() function on Types\Arrays

    Implement indexBy() function on Types\Arrays

    rel: https://github.com/Anahkiasen/underscore-php/issues/33

    I would like to implement Types\Arrays::indexBy() that is implemented in underscore.js. It's _.indexBy() work like this:

    > var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
    undefined
    > u.indexBy(stooges, 'age');
    { '40': { name: 'moe', age: 40 },
      '50': { name: 'larry', age: 50 },
      '60': { name: 'curly', age: 60 } }
    >
    > u.indexBy(stooges, 'hoge');
    { undefined: { name: 'curly', age: 60 } }
    

    In case of non-existing key is specified, underscore.js's _.indexBy() return last element with key undefined (this might not be intentionally).

    But it's better not to include in result in PHP. So,

    $ php -r 'print_r(Arrays::indexOf(..., "hoge");'
    Array
    (
    )
    
    opened by tamakiii 3
  • Adds unique method to Arrays

    Adds unique method to Arrays

    For now it is a very simple function. It uses Array::contains for comparison.

    differences with regular lodash

    • no key for Array of Objects support
    • No sorting option

    I don't if you consider those crucial for the method? I could very easily implement them, no problem.

    opened by Dudemullet 3
  • fix Types\Arrays::group() to return empty array

    fix Types\Arrays::group() to return empty array

    Normally, Types\Arrays::group() works like this.

    <?php
    
    require __DIR__ . '/../vendor/autoload.php';
    
    use Underscore\Types\Arrays;
    
    $rows = [
        ['id' => 1, 'category' => 'a'],
        ['id' => 2, 'category' => 'b'],
        ['id' => 3, 'category' => 'a']
    ];
    
    var_export(Arrays::group($rows, 'category'));
    
    ❯ php src/arrays_group.php
    array (
      'a' =>
      array (
        0 =>
        array (
          'id' => 1,
          'category' => 'a',
        ),
        1 =>
        array (
          'id' => 3,
          'category' => 'a',
        ),
      ),
      'b' =>
      array (
        0 =>
        array (
          'id' => 2,
          'category' => 'b',
        ),
      ),
    )
    

    When I specify key that some rows in $rows doesn't have corresponded value, Arrays::group() returns result like this. It's hard to predict.

    print_r(Arrays::group($rows, 'unknown'));
    
    ❯ php src/arrays_group.php
    array (
      '' =>
      array (
        '' =>
        array (
          '' =>
          array (
            0 =>
            array (
              'id' => 1,
              'category' => 'a',
            ),
          ),
          0 =>
          array (
            'id' => 2,
            'category' => 'b',
          ),
        ),
        0 =>
        array (
          'id' => 3,
          'category' => 'a',
        ),
      ),
    )
    

    I would like it to return empty array. Because nothing exists for the key unknown.

    print_r(Arrays::group($rows, 'unknown'));
    
    ❯ php src/arrays_group.php
    array (
    )
    
    opened by tamakiii 3
  • Cannot use Underscore\Types\Object

    Cannot use Underscore\Types\Object

    Recently after upgrading to php7.2 I have received the following error "PHP message: PHP Fatal error: Cannot use Underscore\Types\Object as Object because 'Object' is a special class name". I've been noticing this across multiple libraries.

    opened by jarretmoses 1
  • Alternative to underscore-php

    Alternative to underscore-php

    Frustrated by the lack of updates, we built our own Underscore-like functional programming library since none of the tools out there were maintained, easy to use, or well-documented: https://github.com/nextbigsoundinc/dash

    $avgMaleAge = Dash\chain([
    	['name' => 'John', 'age' => 12, 'gender' => 'male'],
    	['name' => 'Jane', 'age' => 34, 'gender' => 'female'],
    	['name' => 'Pete', 'age' => 23, 'gender' => 'male'],
    	['name' => 'Mark', 'age' => 11, 'gender' => 'male'],
    	['name' => 'Mary', 'age' => 42, 'gender' => 'female'],
    ])
    ->filter(['gender', 'male'])
    ->map('age')
    ->average()
    ->value();
    
    echo "Average male age is $avgMaleAge.";
    

    Highlights:

    • Many data types supported: arrays, objects, generators (coming soon), Traversable, DirectoryIterator, and more
    • Chaining
    • Currying
    • Lazy evaluation
    • Custom operations
    • Well-tested: Comprehensive tests with nearly 3,000 test cases and 100% code coverage

    I hope it can be useful to someone. Feedback welcome!

    opened by mpetrovich 2
  • Incompatible PHP requirements

    Incompatible PHP requirements

    When installing underscore-php v2.0.0, it reports that it requires php: >=5.4.0 in its composer.json, however at the same time it requires doctrine/inflector (^1.0) which installs v1.3.0 and it requires PHP ^7.1.

    Test on empty folder:

    $ composer require anahkiasen/underscore-php:2.0.0
    $ find vendor -name \*.php -exec php56 -l {} ';'
    No syntax errors detected in vendor/composer/ClassLoader.php
    PHP Parse error:  syntax error, unexpected ':', expecting ';' or '{' in vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php on line 265
    
    Parse error: syntax error, unexpected ':', expecting ';' or '{' in vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php on line 265
    Errors parsing vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php
    No syntax errors detected in vendor/patchwork/utf8/src/Normalizer.php
    ...
    
    opened by kenorb 0
  • Array::reject

    Array::reject

    On your site under Array::reject there is a typo in the method usage example. The example uses Array::filter method insted of Array::reject The example says:

    Arrays::filter(array(1, 2, 3), function($value) {
        return $value % 2 != 0; // Returns array(2)
    });
    

    It should rather say:

    Arrays::reject(array(1, 2, 3), function($value) {
        return $value % 2 != 0; // Returns array(2)
    });
    
    opened by bantya 0
Owner
Emma Fabre
Developer for @madewithlove Mother of Dragibus and Protector of the Realm
Emma Fabre
A composer plugin, to install differenty types of composer packages in custom directories outside the default composer default installation path which is in the vendor folder.

composer-custom-directory-installer A composer plugin, to install differenty types of composer packages in custom directories outside the default comp

Mina Nabil Sami 136 Dec 30, 2022
Here is the top 100 PHP functions: it is the list of the most often used PHP native functions

Here is the top 100 PHP functions: it is the list of the most often used PHP native functions. If you are a PHP developer, you must know the Top 100 PHP Functions deeply.

Max Base 16 Dec 11, 2022
Magento-Functions - A Resource of Magento Functions

Magento-Functions A Resource of Magento Functions Table of Contents Category Product User Cart Checkout General Account [Working w/ URL's] (#urls) Cat

Bryan Littlefield 28 Apr 19, 2021
Laravel Pipeline with DB transaction support, events and additional methods

Laravel Enhanced Pipeline Laravel Pipeline with DB transaction support, events and additional methods #StandWithUkraine Installation Install the packa

Michael Rubél 33 Dec 3, 2022
Provide CSV, JSON, XML and YAML files as an Import Source for the Icinga Director and optionally ship hand-crafted additional Icinga2 config files

Icinga Web 2 Fileshipper module The main purpose of this module is to extend Icinga Director using some of it's exported hooks. Based on them it offer

Icinga 25 Sep 18, 2022
OctoberCMS BlogHub Plugin - Extends RainLab's Blog extension with custom meta details, additional archives and more.

BlogHub extends the RainLab.Blog OctoberCMS plugin with many necessary and helpful features such as Moderatable Comments, Promotable Tags, Custom Meta Fields, additional Archives, basic Statistics, Views counter and more.

rat.md 5 Dec 15, 2022
This script allows to bypass Oracle Cloud Infrastructure 'Out of host capacity' error immediately when additional OCI capacity will appear in your Home Region / Availability domain.

Resolving Oracle Cloud "Out of Capacity" issue and getting free VPS with 4 ARM cores / 24GB of memory Very neat and useful configuration was recently

Alexander Hitrov 323 Jan 6, 2023
Auto-expiring tags with additional payload data on any eloquent model.

Laravel TempTag Auto-expiring tags with additional payload data on any eloquent model. Installation first you need to install and configure mongodb in

masoud nazarpoor 2 Sep 18, 2021
Magento 2 module for displaying additional information in configuration

AvS_ScopeHint for Magento 2 Displays a hint when a configuration value is overwritten on a lower scope (website or store view). Facts version: 1.0.0-b

Andreas von Studnitz 131 Dec 14, 2022
It's a CodeIgniter Library for Captcha Generation. With additional Flexibility .

Original Source : Pavel Tzonkov <[email protected]> Source Link : http://gscripts.net/free-php-scripts/Anti_Spam_Scripts/Image_Validator/d

Chris # 4 Sep 2, 2022
📦 "PHP type names" contains the list of constants for the available PHP data types.

PHP type names PHP type names ?? Description Simple library containing the list of constants for the available PHP data types. Use those constant type

♚ PH⑦ de Soria™♛ 4 Dec 15, 2022
Dependency graph visualization for composer.json (PHP + Composer)

clue/graph-composer Graph visualization for your project's composer.json and its dependencies: Table of contents Usage graph-composer show graph-compo

Christian Lück 797 Jan 5, 2023
Port of the Java Content Repository (JCR) to PHP.

PHP Content Repository PHPCR This repository contains interfaces for the PHPCR standard. The JSR-283 specification defines an API for a Content Reposi

PHPCR 436 Dec 30, 2022
A simple, type-safe, zero dependency port of the javascript fetch WebApi for PHP.

A simple, type-safe, zero dependency port of the javascript fetch WebApi for PHP.

Matias Navarro Carter 105 Jan 4, 2023
Back the fun of reading - PHP Port for Arc90′s Readability

PHP Readability Library If you want to use an up-to-date version of this algorithm,check this newer project: https://github.com/andreskrey/readability

明城 517 Nov 18, 2022
An improved version of the PHP port of KuzuhaScript

KuzuhaScriptPHP+ (くずはすくりぷとPHP+) An improved version of the PHP port of KuzuhaScript (くずはすくりぷと). To my knowledge, it works with PHP version 4.1.0 and a

Heyuri 4 Nov 16, 2022
A PHP port of Ruby's Liquid Templates

Liquid template engine for PHP Liquid is a PHP port of the Liquid template engine for Ruby, which was written by Tobias Lutke. Although there are many

Alexander Guz 141 Nov 4, 2022
World countries - available in multiple languages, in CSV, JSON, PHP, SQL and XML formats

Constantly updated lists of world countries and their associated alpha-2, alpha-3 and numeric country codes as defined by the ISO 3166 standard, available in CSV, JSON , PHP, SQL and XML formats, in multiple languages and with national flags included; also available are the ISO 3166-2 codes of provinces/ states associated with the countries

Stefan Gabos 1k Dec 29, 2022
Ied plugin composer - Inspired Plugin Composer: Create, publish and edit plugins from within Textpattern CMS.

ied_plugin_composer Create, publish and edit plugins from within Textpattern CMS. Creates a new page under the Extensions tab where you can edit and e

Stef Dawson 8 Oct 3, 2020