Say No to Loop!

Paste_Image.png

本文會介紹下Eloquent\Collection,這個是什么呢?這是我們平常使用Eloquent中get,all返回的結(jié)果集菩暗。

collection緣來

我們首先來看一段簡單的代碼:

$books = Book::all();
$titles = [];
foreach ($books as $book){
  if ($book->pages_count > 8){
    $titles[] =  $book->title;
  }
}

這段代碼意圖其實非常明確惑折,就是獲取超過8的書名,再看下面一段代碼:

$titles = [];
foreach ($books as $book){
  if ($book->publisher_id == 2){
    $titles[] =  $book->title;
  }
}

此處是獲取作者是2的書名归园,所有這些代碼都有同樣的loop邏輯黄虱,我們完全可以抽取出來,于是就有了下面的函數(shù):

function map($input, $func)
{
    $result = [];
    foreach ($input as $each)
    {
        $result[] = $func($each);
    }
    return $result;
}
map($books, function($book){
  if ($book->publisher_id == 2){
    return $book->title;
  }
});

這只是展示了一個簡單的map模式庸诱,還有其他更多方便的集合操作方法悬钳,我們將其抽取出來,于是就出現(xiàn)了Collection偶翅,網(wǎng)上有個講Collection的課程默勾,不過太貴了,買不起聚谁。

其實Collection的總體思想感覺就是函數(shù)式編程母剥,Tell, Don’t Ask,客戶端在使用上不再是想著怎么做how,而是想著what to do环疼,一直有個神一樣存在的系列文章沒去讀习霹,今天看到collection的文章,有了沖動去看的炫隶,文章地址:Category Theory for Programmers: The Preface淋叶,等最近看完orm系列就去看這個的。我們還是接著講collection伪阶。

collection使用

在使用collection的原則上煞檩,我們遵守當代碼出現(xiàn)loop的時候,我們就停下來想下栅贴,是否可以通過collection來解決斟湃。

first

三種使用方式

    public function testFirstReturnsFirstItemInCollection()
    {
        $c = new Collection(['foo', 'bar']);
        $this->assertEquals('foo', $c->first());
    }

    public function testFirstWithCallback()
    {
        $data = new Collection(['foo', 'bar', 'baz']);
        $result = $data->first(function ($value) {
            return $value === 'bar';
        });
        $this->assertEquals('bar', $result);
    }

    public function testFirstWithCallbackAndDefault()
    {
        $data = new Collection(['foo', 'bar']);
        $result = $data->first(function ($value) {
            return $value === 'baz';
        }, 'default');
        $this->assertEquals('default', $result);
    }

last

和first使用方式相同

    public function testLastReturnsLastItemInCollection()
    {
        $c = new Collection(['foo', 'bar']);
        $this->assertEquals('bar', $c->last());
    }

    public function testLastWithCallback()
    {
        $data = new Collection([100, 200, 300]);
        $result = $data->last(function ($value) {
            return $value < 250;
        });
        $this->assertEquals(200, $result);
        $result = $data->last(function ($value, $key) {
            return $key < 2;
        });
        $this->assertEquals(200, $result);
    }

    public function testLastWithCallbackAndDefault()
    {
        $data = new Collection(['foo', 'bar']);
        $result = $data->last(function ($value) {
            return $value === 'baz';
        }, 'default');
        $this->assertEquals('default', $result);
    }

map

map是對loop的抽離,對于集合中每個元素做完操作后檐薯,再返回新的元素凝赛。

    public function testMap()
    {
        $data = new Collection(['first' => 'taylor', 'last' => 'otwell']);
        $data = $data->map(function ($item, $key) {
            return $key.'-'.strrev($item);
        });
        $this->assertEquals(['first' => 'first-rolyat', 'last' => 'last-llewto'], $data->all());
    }

each

遍歷元組進行操作,不返回元素操作后的結(jié)果坛缕,當遇到返回false的時候墓猎,結(jié)束遍歷。

    public function testEach()
    {
        $c = new Collection($original = [1, 2, 'foo' => 'bar', 'bam' => 'baz']);

        $result = [];
        $c->each(function ($item, $key) use (&$result) {
            $result[$key] = $item;
        });
        $this->assertEquals($original, $result);

        $result = [];
        $c->each(function ($item, $key) use (&$result) {
            $result[$key] = $item;
            if (is_string($key)) {
                return false;
            }
        });
        $this->assertEquals([1, 2, 'foo' => 'bar'], $result);
    }

filter

遍歷集合赚楚,只將符合條件的留下毙沾,集合中元素的性質(zhì)不會變,如果集合中是product直晨,返回的也是product搀军,不會像map那樣,返回price

    public function testFilter()
    {
        $c = new Collection([['id' => 1, 'name' => 'Hello'], ['id' => 2, 'name' => 'World']]);
        $this->assertEquals([1 => ['id' => 2, 'name' => 'World']], $c->filter(function ($item) {
            return $item['id'] == 2;
        })->all());

        $c = new Collection(['', 'Hello', '', 'World']);
        $this->assertEquals(['Hello', 'World'], $c->filter()->values()->toArray());

        $c = new Collection(['id' => 1, 'first' => 'Hello', 'second' => 'World']);
        $this->assertEquals(['first' => 'Hello', 'second' => 'World'], $c->filter(function ($item, $key) {
            return $key != 'id';
        })->all());
    }

reduce

reduce將一個集合中的元素做遍歷勇皇,返回為一個單子的元素

public function testReduce()
{
    $data = new Collection([1, 2, 3]);
    $this->assertEquals(6, $data->reduce(function ($carry, $element) {
        return $carry += $element;
    }));
}

flatten

flatten意為平坦罩句,可以將任意嵌套的array變?yōu)橥瑢蛹壍模ㄟ^參數(shù)depth敛摘,可以指定平坦的層級

public function testFlatten()
{
    // Flat arrays are unaffected
    $c = new Collection(['#foo', '#bar', '#baz']);
    $this->assertEquals(['#foo', '#bar', '#baz'], $c->flatten()->all());

    // Nested arrays are flattened with existing flat items
    $c = new Collection([['#foo', '#bar'], '#baz']);
    $this->assertEquals(['#foo', '#bar', '#baz'], $c->flatten()->all());

    // Sets of nested arrays are flattened
    $c = new Collection([['#foo', '#bar'], ['#baz']]);
    $this->assertEquals(['#foo', '#bar', '#baz'], $c->flatten()->all());

    // Deeply nested arrays are flattened
    $c = new Collection([['#foo', ['#bar']], ['#baz']]);
    $this->assertEquals(['#foo', '#bar', '#baz'], $c->flatten()->all());
}
public function testFlattenWithDepth()
{
  // No depth flattens recursively
  $c = new Collection([['#foo', ['#bar', ['#baz']]], '#zap']);
  $this->assertEquals(['#foo', '#bar', '#baz', '#zap'], $c->flatten()->all());

  // Specifying a depth only flattens to that depth
  $c = new Collection([['#foo', ['#bar', ['#baz']]], '#zap']);
  $this->assertEquals(['#foo', ['#bar', ['#baz']], '#zap'], $c->flatten(1)->all());

  $c = new Collection([['#foo', ['#bar', ['#baz']]], '#zap']);
  $this->assertEquals(['#foo', '#bar', ['#baz'], '#zap'], $c->flatten(2)->all());
}

FlatMap

flatMap類似于做了先map后flat的操作

public function testFlatMap()
{
    $data = new Collection([
        ['name' => 'taylor', 'hobbies' => ['programming', 'basketball']],
        ['name' => 'adam', 'hobbies' => ['music', 'powerlifting']],
    ]);
    $data = $data->flatMap(function ($person) {
        return $person['hobbies'];
    });
    $this->assertEquals(['programming', 'basketball', 'music', 'powerlifting'], $data->all());
}

zip

吧兩個結(jié)構(gòu)一樣的array像拉拉鏈一樣做合并

public function testZip()
{
    $c = new Collection([1, 2, 3]);
    $c = $c->zip(new Collection([4, 5, 6]));
    $this->assertInstanceOf(Collection::class, $c);
    $this->assertInstanceOf(Collection::class, $c[0]);
    $this->assertInstanceOf(Collection::class, $c[1]);
    $this->assertInstanceOf(Collection::class, $c[2]);
    $this->assertCount(3, $c);
    $this->assertEquals([1, 4], $c[0]->all());
    $this->assertEquals([2, 5], $c[1]->all());
    $this->assertEquals([3, 6], $c[2]->all());

    $c = new Collection([1, 2, 3]);
    $c = $c->zip([4, 5, 6], [7, 8, 9]);
    $this->assertCount(3, $c);
    $this->assertEquals([1, 4, 7], $c[0]->all());
    $this->assertEquals([2, 5, 8], $c[1]->all());
    $this->assertEquals([3, 6, 9], $c[2]->all());

    $c = new Collection([1, 2, 3]);
    $c = $c->zip([4, 5, 6], [7]);
    $this->assertCount(3, $c);
    $this->assertEquals([1, 4, 7], $c[0]->all());
    $this->assertEquals([2, 5, null], $c[1]->all());
    $this->assertEquals([3, 6, null], $c[2]->all());
}

pluck

pluck接受兩個參數(shù)门烂,如果傳遞了第二個參數(shù),則以第二個參數(shù)為key

public function testPluckWithArrayAndObjectValues()
{
    $data = new Collection([(object) ['name' => 'taylor', 'email' => 'foo'], ['name' => 'dayle', 'email' => 'bar']]);
    $this->assertEquals(['taylor' => 'foo', 'dayle' => 'bar'], $data->pluck('email', 'name')->all());
    $this->assertEquals(['foo', 'bar'], $data->pluck('email')->all());
}

更詳細的可以參考https://laravel.tw/docs/5.2/collections

一些建議

我們使用collection的取值的時候兄淫,如果沒有對應的值屯远,我們可以提供default值,此時可以在default中直接拋出異常

return $this->checkers->first(function ($i, $checker) use ( $file) {
    return $checker->canCheck($file);
}, function () {
    throw new Exception("No matching style checker found!");
});

我們有時候為了連貫操作捕虽,即使前一個出錯了慨丐,我們也不希望返回一個null object,我們希望能返回一個空對象泄私,但是這個對象實現(xiàn)了一個空操作房揭,意圖如下:

$this->getObject($input)->check();

此處getObject($input)可能返回是一個實現(xiàn)了check操作的空對象备闲,這時候就可以使用Macroable trait 的東西。

public function testMacroable()
{
    // Foo() macro : unique values starting with A
    Collection::macro('foo', function () {
        return $this->filter(function ($item) {
            return strpos($item, 'a') === 0;
        })
            ->unique()
            ->values();
    });

    $c = new Collection(['a', 'a', 'aa', 'aaa', 'bar']);

    $this->assertSame(['a', 'aa', 'aaa'], $c->foo()->all());
}

更多內(nèi)容大家可以去看文章Refactoring to Collection?—?Notes

參考

Refactoring to Collection?—?Notes

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捅暴,一起剝皮案震驚了整個濱河市恬砂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蓬痒,老刑警劉巖泻骤,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異梧奢,居然都是意外死亡狱掂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門粹断,熙熙樓的掌柜王于貴愁眉苦臉地迎上來符欠,“玉大人嫡霞,你說我怎么就攤上這事瓶埋。” “怎么了诊沪?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵养筒,是天一觀的道長。 經(jīng)常有香客問我端姚,道長晕粪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任渐裸,我火速辦了婚禮巫湘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昏鹃。我一直安慰自己尚氛,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布洞渤。 她就那樣靜靜地躺著阅嘶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪载迄。 梳的紋絲不亂的頭發(fā)上讯柔,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音护昧,去河邊找鬼魂迄。 笑死,一個胖子當著我的面吹牛惋耙,可吹牛的內(nèi)容都是我干的捣炬。 我是一名探鬼主播慈格,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遥金!你這毒婦竟也來了浴捆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤稿械,失蹤者是張志新(化名)和其女友劉穎选泻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體美莫,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡页眯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了厢呵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窝撵。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖襟铭,靈堂內(nèi)的尸體忽然破棺而出碌奉,到底是詐尸還是另有隱情,我是刑警寧澤寒砖,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布赐劣,位于F島的核電站,受9級特大地震影響哩都,放射性物質(zhì)發(fā)生泄漏魁兼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一漠嵌、第九天 我趴在偏房一處隱蔽的房頂上張望咐汞。 院中可真熱鬧,春花似錦儒鹿、人聲如沸化撕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侯谁。三九已至,卻和暖如春章钾,著一層夾襖步出監(jiān)牢的瞬間墙贱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工贱傀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惨撇,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓府寒,卻偏偏與公主長得像魁衙,于是被迫代替她去往敵國和親报腔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容