今日もヤバさをI/O中。

(物理的)大型エンジニアのブログです。基本的に何かが足りません。

allメソッド Collectionメソッドソースコードリーディング100本ノック1本目

メソッド概要

コレクション 5.8 (翻訳中)Laravel

allメソッドはコレクションの裏の配列表現を返します。

<?php

collect([1, 2, 3])->all();

// [1, 2, 3]

裏の配列表現なんて仰々しい事を言っていますが、単純にコレクションの中身を配列で返すだけのメソッドです。

ソースコードリーディング

allメソッド

では早速、ソースコードリーディングをやって行きましょう。

<?php

/**
 * Get all of the items in the collection.
 *
 * @return array
 */
public function all()
{
    return $this->items;
}

items プロパティの値を取得する。以上。第3部完ッ!!

…これだけだと切なすぎるので、items プロパティに何の値が代入されているのか construct を見ましょう。

Collection の construct

<?php

/**
 * Create a new collection.
 *
 * @param  mixed  $items
 * @return void
 */
public function __construct($items = [])
{
    $this->items = $this->getArrayableItems($items);
}

$items を引数にして、getArrayableItems メソッドを呼び出しています。

とりあえず getArrayableItems メソッドの中身を覗きましょう。

getArrayableItemsメソッド

<?php

/**
 * Results array of items from Collection or Arrayable.
 *
 * @param  mixed  $items
 * @return array
 */
protected function getArrayableItems($items)
{
    if (is_array($items)) {
        return $items;
    } elseif ($items instanceof self) {
        return $items->all();
    } elseif ($items instanceof Arrayable) {
        return $items->toArray();
    } elseif ($items instanceof Jsonable) {
        return json_decode($items->toJson(), true);
    } elseif ($items instanceof JsonSerializable) {
        return $items->jsonSerialize();
    } elseif ($items instanceof Traversable) {
        return iterator_to_array($items);
    }

    return (array) $items;
}

CollectionArrayable から配列を作成するメソッドのようです。 条件式が複数あるので、一つずつ何をしているのか処理を追っていきましょう。

条件式:is_array($items)

<?php

if (is_array($items)) {
    return $items;
}

$items が配列だったら、そのまま $items を返します。 配列なら、わざわざ変換する必要はありませんからね。

条件式:$items instanceof self

<?php

} elseif ($items instanceof self) {
    return $items->all();
}

$itemsインスタンスself ( Collection ) だったら、all メソッドを実行しその返り値を返します。

ここで、ちょっとした例を出してみます。

<?php

// IN: $items = new Collection([1])
$this->items = $this->getArrayableItems(new Collection([1]));
$this->items = new Collection([1])->all();
$this->items = $this->getArrayableItems([1]);
$this->items = [1];


// IN: $items = new Collection(new Collection([1]))
$this->items = $this->getArrayableItems(new Collection(new Collection([1])));
$this->items = new Collection(new Collection([1]))->all();
$this->items = $this->getArrayableItems(new Collection([1]));
$this->items = new Collection([1])->all();
$this->items = $this->getArrayableItems([1]);
$this->items = [1];

つまり、2次元の Collection でも3次元の Collection でも、1次元の Collection にしてくれます。以下が実際に実行した結果です。

<?php

dd(new Collection(new Collection([1])));
Collection {#450 ▼
  #items: array:1 [▼
    0 => 1
  ]
}

dd(new Collection(new Collection(new Collection([1]))));
Collection {#450 ▼
  #items: array:1 [▼
    0 => 1
  ]
}

条件式:$items instanceof Arrayable

<?php

} elseif ($items instanceof Arrayable) {
    return $items->toArray();
} 

$itemsインスタンスArrayable だったら、toArray メソッドを実行しその返り値を返します。

Arrayable

Arrayable は、Laravel が提供している interface です。

<?php

namespace Illuminate\Contracts\Support;

interface Arrayable
{
    /**
     * Get the instance as an array.
     *
     * @return array
     */
    public function toArray();
}

toArray メソッドのみが定義されています。

別クラスに Arrayableimplements した上で、変換処理を記述した toArray メソッドを実装する、という使い方でしょうか。

配列じゃない別クラスでも toArray メソッドを使うことで、配列へ変換できるようにするため用意されている interface のようですね。

条件式:$items instanceof Jsonable

<?php

} elseif ($items instanceof Jsonable) {
    return json_decode($items->toJson(), true);
}

$itemsインスタンスJsonable だったら、json_decode 関数を実行しその返り値を返します。

json_decode 関数の第二引数が true なので、$items は配列にデコードされます。

Jsonable

jsonable は、 Laravel が提供している interface です。

<?php

namespace Illuminate\Contracts\Support;

interface Jsonable
{
    /**
     * Convert the object to its JSON representation.
     *
     * @param  int  $options
     * @return string
     */
    public function toJson($options = 0);
}

toJson メソッドのみが定義されています。

こちらも別クラスに Jsonableimplements した上で、変換処理を記述した toJson メソッドを実装する、という使い方でしょうか。

json 形式じゃない別クラスでも toJson メソッドを使うことで、json 形式へ変換できるようにするため用意されている interface のようですね。

Arrayable とちょっとだけ似ている気がします。

条件式:$items instanceof JsonSerializable

<?php

} elseif ($items instanceof JsonSerializable) {
    return $items->jsonSerialize();
}

$itemsインスタンスJsonSerializable だったら、jsonSerialize メソッドを実行しその返り値を返します。

JsonSerializable

JsonSerializablephp が提供している interface です。 ( PHP: JsonSerializable - Manual )

<?php

/**
 * Objects implementing JsonSerializable
 * can customize their JSON representation when encoded with
 * <b>json_encode</b>.
 * @link http://php.net/manual/en/class.jsonserializable.php
 */
interface JsonSerializable  {

    /**
    * Specify data which should be serialized to JSON
    * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
    * @return mixed data which can be serialized by <b>json_encode</b>,
    * which is a value of any type other than a resource.
    * @since 5.4.0
    */
    public function jsonSerialize ();

}

jsonSerialize 関数のみが定義されています。( PHP: JsonSerializable::jsonSerialize - Manual )

別クラスに JsonSerializableimplements した上で、 jsonSerialize 関数を定義し、配列への変換処理を記述する。

使い方自体は ArrayableJsonable と一緒のようですね。

条件式:$items instanceof Traversable

<?php

} elseif ($items instanceof Traversable) {
    return iterator_to_array($items);
}

$itemsインスタンスTraversable だったら、iterator_to_array 関数を実行しその返り値を返します。

Traversable

Traversablephp が提供している interface です。( PHP: Traversable - Manual )

<?php

/**
 * @link https://wiki.php.net/rfc/iterable
 */
interface iterable {}

/**
 * Interface to detect if a class is traversable using &foreach;.
 * @link http://php.net/manual/en/class.traversable.php
 */
interface Traversable extends iterable {
}

iterable を継承していることと( PHP: rfc:iterable )、コメントの内容から foreach で繰り返し可能なクラスに実装するようです。

抽象 interface なので Traversable 単体では動きません。ですので、Traversable を継承している Iterator もしくは IteratorAggregate に、foreach をした時の細かい挙動を実装する必要があります。

その上で、iterator_to_array を使って配列へと変換していきます。( PHP: iterator_to_array - Manual )

<?php

/**
 * Copy the iterator into an array
 * @link http://php.net/manual/en/function.iterator-to-array.php
 * @param Traversable $iterator <p>
 * The iterator being copied.
 * </p>
 * @param bool $use_keys [optional] <p>
 * Whether to use the iterator element keys as index.
 * </p>
 * @return array An array containing the elements of the iterator.
 * @since 5.1.0
 */
function iterator_to_array ($iterator, $use_keys = true) {}

条件式:上記に当てはまらないとき

<?php

return (array) $items;

上記に当てはまらなかった時は、配列に漢のキャストをします。

最後に

と言うわけで、Collection メソッドソースコードリーディング100本ノック1本目が終わりました!

all メソッドの内容だけでは寂しいと深掘っていったら、Laravel どころか php の内部実装まで触れてしまいました。

次は avgaverage メソッドです!

マサカリや「ここちげえぞ!!!」と言う怒りのコメントまで、お待ちしております!