`LINQ to Object` inspired DSL for PHP

Overview

Ginq

Array handling in PHP? Be happy with Ginq!

Ginq is a DSL that can handle arrays and iterators of PHP unified.

Ginq is inspired by Linq to Object, but is not a clone.

Many functions in Ginq are evaluated in lazy, and no actions are taken until that time. This features bring you many benefits.

Install

composer.json:

{
    "require": {
        "ginq/ginq": "dev-master"
    }
}

see: https://packagist.org/packages/ginq/ginq

Usage

$xs = Ginq::from(array(1,2,3,4,5,6,7,8,9,10))
        ->where(function($x) { return $x % 2 != 0; })
        ->select(function($x) { return $x * $x; });
        ;

You pass Ginq data and build a query with it. In this example above, you order Ginq to choose even numbers and square each of them.

But Ginq do nothing, Ginq knows only you want a result of chosen and squared numbers.

Let's execute foreach loop with Ginq to get the result.

foreach ($xs as $x) { echo "$x "; }

The result is

1 9 25 49 81

You got the expected result!

Next, you can get an array with toList.

$xs->toList();
array(1,9,25,49,81);

Ginq has functions, well-known in SQL, such as join(), orderBy(), and groupBy() other than select(), where() listed above.

Selector and Predicate

Most of methods in Ginq receive a closure as a argument.

You may not be familiar with closures, but it is very simple things. There are just three types of closures in Ginq, you can remember simply. These are predicate, selector, and connection selector.

Predicate

A closure that passed to a method that do select, such as where() is called predicate.

Predicate is a closure that receive a pair of key and values in the elements and return boolean value.

function ($v, [$k]) { return $v % 2 == 0; }

You get even numbers when you pass this closure to where(). You can skip second argument when you don't need it in the process.

Selector

A closure that passed to a method that do projection, such as select() is called selector.

Selector is a closure that receive a pair of key and value in the elements and create a new value or key, and then return it.

function ($v, [$k]) { return $v * $v ; }

You get squared numbers of original when you pass this closure to select().

This function is used to specify the key of grouping with groupBy(), the key of sorting with groupBy().

Connection Selector

Connection Selector is one of the selector that combine two elements into one, is used with join(), zip().

function ($v0, $v1, [$k0, $k1]) { return array($v0, $v1) ; }

This function receive 4 arguments, two values and two keys, and then create new value or key and return it. You can skip arguments when you don't need it in the process.

These are zip() example that combine each elements from two arrays.

$foods  = array("meat", "pasta", "salada");
$spices = array("time", "basil", "dill");

$xs = Ginq::from($foods)
        ->zip($spices, function($f, $s) {
            return "$f with $s!";
        })
        ;

foreach ($xs as $x) { echo "$x\n"; }
meat with time!
pasta with basil!
salada with dill!

Shortcuts of predicate and selector

Selector can receive a character string instead of a closure.

They return the value of the field when the element is an object, or return the value of the key when it is an array.

So,

Ginq::from($xs)->select('[key].property');

The example above is same as two examples below.

Ginq::from($xs)->select(
    function ($v, $k) { return $v['key'].property; }
);

see: Property Access (Symfony) http://symfony.com/doc/current/components/property_access/index.html

More complex examples

References

Development

PreRequirements

  • Docker installed.
  • Docker Compose installed.

How to start development (Run test)

  • cd docker
  • docker-compose up -d
  • docker-compose exec php ash
  • (in php container)
    • composer install
    • vendor/bin/phpunit

Note

Comments
  • [PHP7.4] DelegateJoinSelector # select で return $f($v0, $v1, $k0, $k1); できない場合がある

    [PHP7.4] DelegateJoinSelector # select で return $f($v0, $v1, $k0, $k1); できない場合がある

    PHP 7.4.13 の環境にて https://zenn.dev/sogaoh/scraps/0898c2ef617dcf のような経緯でデバッグを行っているのですが、 題名の通りの問題が発生してしまっています。

    すべては書けないのですが、渡ってきているデータは以下のようなものです

    $v0, $v1, $k0, $k1 の var_dump
    array(1) {
      ["v0"]=>
      array(22) {
        ["i_type"]=>
        int(1)
        ["reason"]=>
        string(1) "f"
        ["fk_m_id"]=>
        int(1)
        ["fk_p_id"]=>
        int(1)
        ["b_type"]=>
        int(2)
        ["b_date"]=>
        string(19) "2019-12-09 00:00:00"
        ["b_end_date"]=>
        string(19) "2019-12-09 00:00:00"
        ["net"]=>
        int(10000)
        ["gross"]=>
        int(100000)
        ["remarks"]=>
        string(1) "t"
        ["user_type"]=>
        int(1)
        ["created_at"]=>
        object(Illuminate\Support\Carbon)#4192 (16) {
            //略
        }
        ["updated_at"]=>
        object(Illuminate\Support\Carbon)#4196 (16) {
            //略
        }
        ["id"]=>
        int(1)
        ["array_p"]=>
        array(12) {
          //int 8項目
          //string 4項目
        }
        ["array_a"]=>
        array(12) {
          //int 8項目
          //string 4項目
        }
        //その他、いくつか "array_p" や "array_a" のような連想配列
      }
    }
    
    array(1) {
      ["v1"]=>
      array(12) {
        ["id"]=>
        int(1)
        ["fk_a_id"]=>
        int(1)
        ["fk_c_id"]=>
        int(1)
        ["name"]=>
        string(17) "Isabella McKenzie"
        ["fk_g_id"]=>
        int(1)
        ["fk_ct_id"]=>
        int(1)
        ["c_p"]=>
        string(12) "xxxxxxxxxxxx"
        ["r_c_type"]=>
        int(0)
        ["user_type"]=>
        int(3)
        ["user_id"]=>
        int(1)
        ["created_at"]=>
        string(19) "2020-12-10 20:40:58"
        ["updated_at"]=>
        string(19) "2020-12-10 20:40:58"
      }
    }
    
    array(1) {
      ["k0"]=>
      int(0)
    }
    
    array(1) {
      ["k1"]=>
      int(0)
    }
    

    「落ちる」のは↓だと思っています。 https://github.com/akanehara/ginq/blob/master/src/Ginq/JoinSelector/DelegateJoinSelector.php#L46

    解決方法を考えたいです。

    opened by sogaoh 8
  • orElse系の代替値

    orElse系の代替値

    *OrElse 系で代替値の評価自体を遅延したいケースもあるはずなので、 $default に関数もとれるようにする。

    /**
     * @param mixed|\Closure $default () -> mixed
     * @param callable|null      $predicate (v, k) -> bool
     * @return mixed
     */
    public function firstOrElse($default, $predicate = null);
    
    /**
     * @param int                   $index
     * @param mixed|\Closure $default (i) -> mixed
     * @return mixed
     */
    public function getValueAtOrElse($index, $default);
    
    enhancement 
    opened by akanehara 7
  • Add support for expressions

    Add support for expressions

    It would be amazing to add a shorthand notation for closures:

    $xs = Ginq::from(array(1,2,3))->where('x % 2 != 0')->select('x * x');
    

    and/or

    $xs = Ginq::from(array(1,2,3))
             ->where(['x' => 'x % 2 != 0'])->select(['v, k' => 'k+v']);
    

    See

    • http://symfony.com/doc/current/components/expression_language/syntax.html
    • https://github.com/michelsalib/BCCEnumerableUtility#use-of-functions
    opened by hason 5
  • Closure未指定のany()メソッドが例外吐く

    Closure未指定のany()メソッドが例外吐く

    ソース見て、なんとなくそんな気はしてたけど、 \Ginq::from([])->any() エラーになるね、これ。

    ...
    #2 {main}
      thrown in D:\work\tests\php\Ginq\Predicate\PredicateParser.php on line 34
    
    Fatal error: Uncaught exception 'InvalidArgumentException' with message ''predicate' callable expected, got NULL'
    ...
    

    ちなみに期待した結果は、false

    opened by ritalin 4
  • Ginq\Context の名前

    Ginq\Context の名前

    https://github.com/akanehara/ginq/tree/feature/ginq-context

    feature/ginq-context ブランチの

    • Ginq\Context
    • Ginq\OrderedContext
    • Ginq\GroupingContext

    を、それぞれ

    • Ginq\Stream
    • Ginq\OrderedStream
    • Ginq\GroupingStream

    にリネームするのはどうかという提案。

    opened by akanehara 3
  • join() の改善

    join() の改善

    Ginq::join() がルックアップテーブルを作るのは、Ginq::join() 実行時ではなく、イテレータの riwind() であるべき。クエリを組み立てるだけで inner の全件走査が動作するのはできる限り実行を遅延するGinqのポリシーに反する。join を selectMany の再利用で実装するのをやめ、専用のイテレータを作る。

    opened by akanehara 2
  • selectManyのセレクタで最初に空のリストが返却されると結果も空になる

    selectManyのセレクタで最初に空のリストが返却されると結果も空になる

    selectManyのセレクタで最初に空のリストが返却されるとselectManyの結果も空になります。

    空のリストが含まれる場合でも次のような場合は

    $data = array(
        array(
            'values' => array(1, 2)
        ),
        array(
            'values' => array()
        ),
        array(
            'values' => array(3, 4)
        )
    );
    
    print_r(Ginq::from($data)->selectMany('[values]')->toList());
    

    以下のように正常な結果が得られます。

    Array
    (
        [0] => 1
        [1] => 2
        [2] => 3
        [3] => 4
    )
    

    しかし、最初に空のリストがある場合

    $data = array(
        array(
            'values' => array()
        ),
        array(
            'values' => array(1, 2)
        ),
        array(
            'values' => array(3, 4)
        )
    );
    
    print_r(Ginq::from($data)->selectMany('[values]')->toList());
    

    以下のように結果は空になってしまいます。

    Array
    (
    )
    
    opened by gunjiro 1
  • Added support for

    Added support for "symfony" notation and functionality in PathSelector

    Related to #34

    In the Symfony is the PropertyAccess component, which provides function to read from an object or array using a simple string notation. The big advantage of this component is that it can call the corresponding methods (getters, hassers, issers, __get, __call) to accessing private properties of an object.

    For more information see http://symfony.com/doc/current/components/property_access/introduction.html

    opened by hason 1
  • joinにまつわる名前の問題

    joinにまつわる名前の問題

    Ginqがイテレータのkeyを意識していなかった頃のjoinの引数は

    join($outerKeySelector, $innerKeySelector, $joinSelector)
    

    で、keyという言葉がは結合キー「だけ」を指していた。

    ところが、現在は反復子のキーにも key という言葉を使っているために

    join(
        $outerKeySelector, $innerKeySelector,
        $valueJoinSelector, $keyJoinSelector
    )
    

    となってしまっており、どれが結合キーを表すのかとてもわかりづらい。

    • 2値をとるセレクタを JoinSelector と呼ぶことの妥当性
    • 結合キーセレクタを表す変数名 outerKeySelector, innerKeySelector の妥当性

    これらについて考えたい。

    opened by akanehara 1
  • Property access syntax causes massive performance drop

    Property access syntax causes massive performance drop

    Your competitor here. :grinning:

    I've run performance tests on YaLinqo, Ginq and Pinq. Whenever I use property access syntax, performance of Ginq queries drops 2-5 times. See gist with performance results (results from YaLinqo master branch). You should cache functions or something. Using "string lambdas" in YaLinqo, which rely on create_function, does decrease performance, but not so significantly (since I've added caching, inner queries stopped killing performance too).

    Just curious, is using explicit iterator classes instead of generators a deliberate design decision, or do you just aim for compatibility with older PHP versions?

    P. S. Thanks for competition. Ginq and Pinq forced me to finally update my library: add missing methods, improve performance, add integration with continuous integration and code quality tools, put reference documentation online etc. :grinning:

    opened by Athari 0
  • distinctBy があると便利かもしれない

    distinctBy があると便利かもしれない

    「何を以って重複とするか」を指定できると便利だな、という場面がありました。 例えば「ある親IDに属するメンバーからひとつのみ選択」という条件を適用する場合

    $picked_parent_ids = [];
    Ginq::from($members)
        ->where(function(Members $m) use (&$picked_parent_ids) {
            if (!in_array($m->parent_id, $picked_parent_ids)) {
                $picked_parent_ids[] = $m->parent_id;
                return true;
            } else {
                return false;
            }
        })
    

    のようにフィルタリングする必要がありますが、これを以下のように表現できるとスッキリします。

    Ginq::from($members)
        ->distinctBy('parent_id');
    
    opened by sukobuto 1
  • Replace hard coded classes with factories or DI container

    Replace hard coded classes with factories or DI container

    I want to use own Ginq class that adds a custom method, but it's impossible. It would be useful to have a environment object that can be passed to each iterator:

    $ginq = new Environment(['Ging' => 'Vendor\Ginq', 'OrderingGinq' => ...]);
    $ginq->from($data)->...;
    

    It can be used pimple DI container - https://github.com/fabpot/Pimple.

    opened by hason 1
Owner
Atsushi Kanehara
Atsushi Kanehara
Yet Another LINQ to Objects for PHP [Simplified BSD]

YaLinqo: Yet Another LINQ to Objects for PHP Online documentation GitHub repository Features The most complete port of .NET LINQ to PHP, with many add

Alexander Prokhorov 436 Jan 3, 2023
Flexible serializer encouraging good object design

Serializard Serializard is a library for (un)serialization of data of any complexity. Its main focus is to give user as much flexibility as possible b

Tomasz Kowalczyk 24 May 27, 2022
True asynchronous PHP I/O and HTTP without frameworks, extensions, or annoying code. Uses the accepted Fibers RFC to be implemented into PHP 8.1

PHP Fibers - Async Examples Without External Dependencies True asynchronous PHP I/O and HTTP without frameworks, extensions, or annoying code behemoth

Cole Green 121 Jan 6, 2023
Map nested JSON structures onto PHP classes

JsonMapper - map nested JSON structures onto PHP classes Takes data retrieved from a JSON web service and converts them into nested object and arrays

Christian Weiske 1.4k Dec 30, 2022
A Collections library for PHP.

A Library of Collections for OO Programming While developing and helping others develop PHP applications I noticed the trend to use PHP's arrays in ne

Levi Morrison 629 Nov 20, 2022
🗃 Array manipulation library for PHP, called Arrayy!

?? Arrayy A PHP array manipulation library. Compatible with PHP 7+ & PHP 8+ \Arrayy\Type\StringCollection::create(['Array', 'Array'])->unique()->appen

Lars Moelleken 430 Jan 5, 2023
Collections Abstraction library for PHP

Collections Collections Abstraction library for PHP The Collection library is one of the most useful things that many modern languages has, but for so

Ítalo Vietro 62 Dec 27, 2021
A repository with implementations of different data structures and algorithms using PHP

PHP Data Structures and Algorithms Data structure and Algorithm is always important for any programming language. PHP, being one of the most popular l

Mizanur Rahman 610 Jan 2, 2023
Leetcode for PHP, five questions a week and weekends are updated irregularly

✏️ Leetcode for PHP why do you have to sleep for a long time ,and naturally sleep after death 联系 说明 由于目前工作主要是 golang,我又新起了一个LeetCode-Go-Week项目,- Leetc

吴亲库里 370 Dec 29, 2022
Missing data types for PHP. Highly extendable.

Neverending data validation can be exhausting. Either you have to validate your data over and over again in every function you use it, or you have to rely it has already been validated somewhere else and risk potential problems.

SmartEmailing 82 Nov 11, 2022
JsonMapper - map nested JSON structures onto PHP classes

Takes data retrieved from a JSON web service and converts them into nested object and arrays - using your own model classes.

Netresearch 9 Aug 21, 2022
All Algorithms implemented in Php

The Algorithms - PHP All algorithms implemented in Php (for education) These implementations are for learning purposes. They may be less efficient tha

The Algorithms 1k Dec 27, 2022
A community driven collection of sorting algorithms in PHP

algorithms A community driven collection of sorting algorithms This repository includes a comma separated file that includes 10k numbers between 1 and

Andrew S Erwin 0 May 16, 2022
Iterators - The missing PHP iterators.

PHP Iterators Description The missing PHP iterators. Features CachingIteratorAggregate ClosureIterator: ClosureIterator(callable $callable, array $arg

(infinite) loophp 24 Dec 21, 2022
PHP Integrated Query, a real LINQ library for PHP

PHP Integrated Query - Official site What is PINQ? Based off the .NET's LINQ (Language integrated query), PINQ unifies querying across arrays/iterator

Elliot Levin 465 Dec 30, 2022
Yet Another LINQ to Objects for PHP [Simplified BSD]

YaLinqo: Yet Another LINQ to Objects for PHP Online documentation GitHub repository Features The most complete port of .NET LINQ to PHP, with many add

Alexander Prokhorov 436 Jan 3, 2023
🖥 Build beautiful PHP CLI menus. Simple yet Powerful. Expressive DSL.

Contents Minimum Requirements Installation Upgrading Usage Quick Setup Examples API Appearance Menu Title Colour Width Padding Margin Borders Exit But

PHP School 1.9k Dec 28, 2022
Blackfire Player is a powerful Web Crawling, Web Testing, and Web Scraper application. It provides a nice DSL to crawl HTTP services, assert responses, and extract data from HTML/XML/JSON responses.

Blackfire Player Blackfire Player is a powerful Web Crawling, Web Testing, and Web Scraper application. It provides a nice DSL to crawl HTTP services,

Blackfire 485 Dec 31, 2022
The component provides an array-based DSL to construct complex validation chains.

Spiral Validator The component provides an array-based DSL to construct complex validation chains. Requirements Make sure that your server is configur

Spiral Scout 2 Sep 14, 2022