コンテンツにスキップ

関数

はじめに

本サイトにつきまして、【IT技術の知見】はじめに - 俺の技術ノート をご認識のほど宜しくお願いいたします。


01. 関数

まず読むべき記事

▼ ゲッターとセッターを関数名の接頭辞とすることは悪

和訳:https://www.kaitoy.xyz/2015/07/22/getters-setters-evil/

原典:https://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html

▼ セッターによる依存性注入は悪

原典:https://www.yegor256.com/2014/10/03/di-containers-are-evil.html


値を取得するアクセサ関数の実装

▼ Getter

Getter では、状態を取得するのみではなく、何かしらの処理を加えたうえで取得すること。

<?php

class ABC {

    private mixed $property = null;

    public function property(): mixed
    {
        // 単なるGetterではなく、例外処理も加える。
        if(!isset($this->property)){
            throw new ErrorException("データに値がセットされていません。");
        }
        return $this->property;
    }

}


値を設定するアクセサ関数の実装

▼ Setter

『ミュータブル』なオブジェクトを実現できる。

<?php

class Test01 {

    private mixed $property01 = null;

    // Setterで$property01に値を設定
    public function setProperty(mixed $property01): void
    {
        $this->property01 = $property01;
    }
}

▼ マジック関数の __construct() 関数

マジック関数の __construct() 関数を持たせることにより、このデータを持っていなければならないとい制約を明示できる。

Setter を持たせずに、__construct() 関数のみを持たせれば、ValueObject のような、『イミュータブル』なオブジェクトを実現できる。

<?php

class Test02 {

    private mixed $property02 = null;

    // コンストラクタで$property02に値を設定
    public function __construct(mixed $property02)
    {
        $this->property02 = $property02;
    }
}

▼ 『ミュータブル』と『イミュータブル』を実現できる理由

Test01 クラスインスタンスの $property01 に値を設定するためには、インスタンスから Setter をコールする。

Setter は何度でも呼び出せ、そのたびにデータの値を上書きできる。

<?php

class Test01
{
    // 中身は省略
}

$test01 = new Test01;

$test01->setProperty01("データ01の値");

$test01->setProperty01("新しいデータ01の値");

一方で、Test02 クラスインスタンスの $property02 に値を設定するためには、インスタンスを作り直さなければならない。

つまり、以前に作ったインスタンスの $property02 の値は上書きできない。

Setter を持たせずに、__construct() 関数のみを持たせれば、『イミュータブル』なクラスとなる。

<?php

class Test02
{
    // 中身は省略
}

$test02 = new Test02("データ02の値");

$test02 = new Test02("新しいデータ02の値");

Entity は、ミュータブルであるため、Setter と __construct() 関数の両方を持てる。

ValueObject は、イミュータブルのため、__construct() 関数しか持てる。


マジック関数 (Getter 系)

オブジェクトに対して特定の操作が行われたときに自動的にコールされる特殊な関数のこと。

自動的に呼び出される仕組みは謎。

共通の処理を実行する Getter (例:値を取得するだけの Getter など) を無闇に増やしたくない場合に使用することで、コード量の肥大化を防げる。

PHP には最初からマジック関数は組み込まれているが、自身で実装した場合、オーバーライドされてコールされる。

▼ __get() 関数

定義されていないデータや、アクセス権のない状態を取得しようとしたとき、代わりに呼び出される。

関数は定義しているが、データは定義していないような状況で使用する。

<?php
class Foo
{

    private array $foo = [];

    // 引数と返却値のデータ型を指定
    public function __get(string $name): string
    {
        return "{$name}データは存在しないため、データ値を取得できません。";
    }

}

// 存在しない状態を取得。
$foo = new Foo();
$foo->foo;

// 結果
// fooデータは存在しないため、値を呼び出せません。

▼ __call() 関数

定義されていない関数や、アクセス権のない関数を取得しようとしたとき、代わりにコールされる。

データは定義しているが、関数は定義していないような状況で使用する。

▼ __callStatic() 関数


マジック関数 (Setter 系)

定義されていない静的関数や、アクセス権のない静的関数を取得しようとしたとき、代わりに呼び出される。

自動的にコールされる仕組みは謎。

共通の処理を実行する Setter (例:値を設定するだけの Setter など) を無闇に増やしたくない場合に使用することで、コード量の肥大化を防げる。

PHP には最初からマジック関数は組み込まれているが、自身で実装した場合、オーバーライドされて呼び出される。

▼ __set() 関数

定義されていないデータや、アクセス権のないデータに値を設定しようとしたとき、代わりにコールされる。

オブジェクトの不変性を実現するために使用される。

<?php
class Foo
{

    private array $foo = [];

    // 引数と返り値のデータ型を指定
    public function __set(string $name, mixed $value): void
    {
        echo "{$name}データは存在しないため、{$value}を設定できません。";
    }

}
<?php

class Foo {
// 中身は省略
}

// 存在しないデータに値をセット。
$foo = new Foo();
$foo->bar = "bar";

// 結果
// barデータは存在しないため、barを設定できません。

▼ マジック関数の __construct() 関数

インスタンス化時に自動的に呼び出される関数。

インスタンス化時に実行したい処理を記述できる。

Setter を持たせずに、__construct() 関数のみで値を設定すれば、ValueObject のような、『イミュータブル』なオブジェクトを実現できる。

<?php

class Test02
{

    private mixed $property02 = null;

    // コンストラクタで$property02に値を設定
    public function __construct(mixed $property02)
    {
        $this->property02 = $property02;
    }

}

▼ 『ミュータブル』と『イミュータブル』を実現できる理由】

Test01 クラスインスタンスの $property01 に値を設定するためには、インスタンスから Setter をコールする。

Setter は何度でもコールでき、そのたびにデータの値を上書きできる。

<?php

$test01 = new Test01;

$test01->setProperty01("データ01の値");

$test01->setProperty01("新しいデータ01の値");

一方で、Test02 クラスインスタンスの $property02 に値を設定するためには、インスタンスを作り直さなければならない。

つまり、以前に作ったインスタンスの $property02 の値は上書きできない。

Setter を持たせずに、__construct() 関数のみを持たせれば、『イミュータブル』なオブジェクトとなる。

<?php

$test02 = new Test02("データ02の値");

$test02 = new Test02("新しいデータ02の値");


マジック関数 (その他)

▼ __invoke() 関数

定義したクラスを、関数のように扱える。

__invoke() 関数自体は無名関数として扱われる。

<?php

class Foo
{
    public function __invoke(array $array): void
    {
        print_r($array);
    }
}

$foo = new Foo();
$foo([1, 2, 3]);

// 結果
// Array
// (
//    [0] => 1
//    [1] => 2
//    [2] => 3
// )

▼ __clone() 関数

<?php

// ここに実装例


インスタンスの作成関数

▼ static() 関数 と self() 関数の違い

どちらも、new 演算子と組み合わせて、自身のインスタンスを返却する関数であるが、作成の対象になるクラスが異なる。

<?php

class A
{

    public static function get_self(): A
    {
        return new self();
    }

    public static function get_static(): static
    {
        return new static();
    }
}
<?php
class B extends A {}

以下の通り、self() 関数は定義したクラスをインスタンス化する。

一方で、static() 関数はコールされたクラスをインスタンス化する。

自身のインスタンス化処理が継承される場合は、static() 関数を使用したほうがよい。

<?php

echo get_class(B::get_self());   // 継承元のクラスA

echo get_class(B::get_static()); // 継承先のクラスB

echo get_class(A::get_static()); // 継承元のクラスA


関数のコール

▼ 関数チェーン

以下のような、オブジェクト A を最外層とした関係が存在しているとする。

【オブジェクト A (オブジェクト B を状態に持つ) 】

<?php

class Obj_A
{
    private ObjB $objB;

    // 返却値のデータ型を指定
    public function objB(): ObjB
    {
        return $this->objB;
    }
}

【オブジェクト B (オブジェクト C を状態に持つ) 】

<?php

class Obj_B
{
    private ObjC $objC;

    // 返却値のデータ型を指定
    public function objC(): ObjC
    {
        return $this->objC;
    }
}

【オブジェクト C (オブジェクト D を状態に持つ) 】

<?php

class Obj_C
{
    private ObjD $objD;

    // 返却値のデータ型を指定
    public function objD(): ObjD
    {
        return $this->objD;
    }
}

以下のように、返却値のオブジェクトを使用して、より深い層に連続してアクセスしていく場合…

<?php

$ObjA = new Obj_A;

$ObjB = $ObjA->objB();

$ObjC = $ObjB->objC();

$ObjD = $ObjC->objD();

以下のように、関数チェーンという書き方が可能。

<?php

$D = objB()->objC()->objD();

// $D には ObjD が格納されている。

▼ Recursive call:再帰的プログラム

自プログラムから自身をコールし、実行できるプログラムのこと。

ある関数 f の定義のなかに f 自身を呼び出している箇所がある。

再帰的

  1. 適当な値を基準値 (Pivot) とする (※できれば中央値が望ましい)

  2. Pivot より小さい数を前方、大きい数を後方に分割する。

  3. 二分割された各々のデータを、それぞれ並び替える。

  4. ソートを繰り返し実行する。

    <?php
    
    class Foo
    {
    
        public function quickSort(array $array): array
        {
            // 配列の要素数が1つしかない場合、クイックソートする必要がないため、返却する。
            if (count($array) <= 1) {
                return $array;
            }
    
            // 一番最初の値をPivotとする。
            $pivot = array_shift($array);
    
            // グループを定義
            $left = $right = [];
    
            foreach ($array as $value) {
    
                if ($value < $pivot) {
    
                    // Pivotより小さい数は左グループに格納
                    $left[] = $value;
                } else {
    
                    // Pivotより大きい数は右グループに格納
                    $right[] = $value;
                }
    
            }
    
            // 処理の周回ごとに、結果の配列を結合。
            return array_merge
            (
            // 左のグループを再帰的にクイックソート。
                $this->quickSort($left),
    
                // Pivotを結果に組み込む。
                [$pivot],
    
                // 右のグループを再帰的にクイックソート。
                $this->quickSort($right)
            );
        }
    }
    
    // 実際に使用してみる。
    $array = [6, 4, 3, 7, 8, 5, 2, 9, 1];
    $result = (new Foo())->quickSort($array);
    var_dump($result);
    
    // 昇順に並び替えられている。
    // 1, 2, 3, 4, 5, 6, 7, 8
    


引数

▼ オプション引数

引数が与えられなければ、指定の値を渡す方法

<?php

// ここに実装例


値を返す前の途中終了

▼ return;

<?php

class Foo
{

    public function returnMethod(): void
    {
        print "returnMethod()です。\n";
        return; // 何も返さない。
    }
}

$foo = new Foo();
$foo->returnMethod(); // returnMethod()です。
// 処理は続く。

▼ exit;

<?php

class Foo
{

    function exitMethod(): void
    {
        print "exitMethod()です。\n";
        exit;
    }
}

$foo = new Foo();
$foo->exitMethod(); // exitMethod()です。
// ここで、ソフトウェア全体の処理が完了する。


値の返却

▼ return

関数がコールされた場所に値を返却した後、その処理が終了する。

▼ yield

関数がコールされた場所に値を返却した後、そこで終了せず、yield の次の処理が続く。

返却値は、Generator 型である。

<?php

class Foo
{

    public function oneToThree(): Generator
    {
        for ($i = 1; $i <= 3; $i++) {
            // yield を返却した後、$i の値が維持される。
            yield $i;
        }
    }
}

$foo = new Foo();
$oneToThree = $foo->oneToThree();

foreach ($oneToThree as $value) {
    echo "{$value}\n";
}

// 1
// 2
// 3


Dispatcher

▼ Dispatcher とは

特定の名前と関数を紐付け、名前を渡すことにより、関数をコールするオブジェクトを Dispatcher という。

<?php

class Dispatcher
{
    // 中身は省略
}

$dispatcher = new Dispatcher;

$name = "foo";

// 名前に紐付ける関数を定義。
$listener = function() use ($param): void{
    // 何らかの処理
};

// 名前と関数の登録.
$dispatcher->addListener($name, $listener);

// 文字列から関数をコール。ついでに、引数を渡す。
$dispatcher->dispatch("foo", "test");

▼ イベント名と関数の紐付け

名前としてイベント名を定義し、これに関数を紐付ける。

特定のイベント名が渡されたとき、それに対応づけられた関数をコールする。

フレームワークの EventDispatcher クラスが簡単である。

<?php

use Symfony\Component\EventDispatcher\EventDispatcher;

class FooEventDispatcher
{

}

▼ 計算処理の返却値を保持するオブジェクト

大サイズのデータを集計するは、その処理に時間がかかる。

そこで、そのような関数では、一度コールされて集計した後、データに返却値を保持しておく。

そして、再びコールされたときには、返却値をデータから取り出す。

<?php

class ResultCacher
{

    private array $resultCollection = [];

    private array $funcCollection;

    public function __construct()
    {
        $this->funcCollection = $this->addListener();
    }

    /**
     * 集計関数
     */
    public function computeRevenue(): void
    {
        // 時間のかかる集計処理;
    }

    public function funcNameListener(string $funcName): mixed
    {
        // 返却値が設定されていなかった場合、値を設定.
        if (!isset($this->resultCollection[$funcName])) {

            // 関数の返却値をCollectionに設定。
            $this->resultCollection[$funcName] = $this->dispatch($funcName);
        }

        // 返却値が設定されていた場合,そのまま使用する。
        return $this->resultCollection[$funcName];
    }

    /**
     * 返却値をキャッシュしたい関数名を登録しておく
     */
    private function addListener(): array
    {
        return [
            "computeRevenue" => [$this, "computeRevenue"]
        ];
    }

    private function dispatch(string $funcName): mixed
    {
        // call_user_funcで関数を実行
        return call_user_func(
        // 登録されている関数名から、関数をDispatch.
            $this->funcCollection[$funcName]
        );
    }
}


02. クロージャー (無名関数)

クロージャー (無名関数) の定義、変数格納後のコール

▼ use() 関数のみに引数を渡す場合

<?php

class Item
{
    // 中身は省略
}

$item = new Item;

// 最初の括弧を使用しないことにより、普段よくやっている値渡しの関数を定義しているのと同じになる。
// use()に、親関数 ($optionName) のスコープの$itemを渡す。
$optionName = function () use ($item): string {
    return $item->optionName();
};

// function()には引数が設定されていないため、コール時に引数は不要。
echo $optionName();

// 出力結果
// オプションA

▼ function() 関数と use() 関数に引数を渡す場合

<?php
class Item
{
    // 中身は省略
}

$item = new Item;

// 最初の括弧を使用しないことにより、普段よくやっている値渡しの関数を定義しているのと同じになる。
// 親関数 ($optionName) のスコープの$itemを、use()に渡す。
// $paramは、コール時に使用する変数。
$optionName = function (string $para) use ($item): string {
    return $item->optionName() . $para;
};

// コール時、$paramをfunction()に渡す。
echo $optionName("BC");

// 出力結果
// オプションABC

▼ データの値として、無名関数を格納しておく裏技

<?php

class Item
{
    // 中身は省略
}

class Option
{
    public Closure $name;
}

$item = new Item;

// 最初の括弧を使用しないことにより、普段よくやっている値渡しの関数を定義しているのと同じになる。
// use()に、親関数のスコープの$itemを渡す。
// $paramは、コール時に使用する変数。
$option = new Option;

// データの値に無名関数を格納する。
$option->name = function (string $para) use ($item): string {
    return $item->optionName() . $para;
};

// コール時、$paramをfunction()に渡す。
echo ($option->name)("BC");

// 出力結果
// オプションABC


クロージャー (無名関数) の定義と即コール

定義したその場でコールされる無名関数を『即時関数』と呼ぶ。

無名関数をコールしたいときは、call_user_func() 関数を使用する。

<?php

class Item
{
    // 中身は省略
}

$item = new Item;
$param = "BC";

// use()に、親関数のスコープの$itemを渡す。
// 無名関数を定義し、同時にcall_user_func()で即コールする。
// $paramは、コール時に使用する変数。
$optionName = call_user_func(function (string $param) use ($item): string {
    return $item->optionName() . $param;
}, $param);

// $paramはすでに即コール時に渡されている。
// これはコールではなく、即コール時に格納された返却値の出力。
echo $optionName;

// 出力結果
// オプションABC


高階関数とクロージャー (無名関数) の組み合わせ

関数を引数として受け取ったり、関数自体を返したりする関数を『高階関数』と呼ぶ。

▼ 無名関数を使用しない場合

<?php

// 第一引数のみの場合
class Foo
{
    /**
     * 高階関数を定義
     */
    public function test(callable $callback): void
    {
        echo $callback();
    }

    /**
     * コールバックを定義
     * 関数の中でコールされるため、『後で呼び出される』という意味合いから、コールバック関数といえる。
     */
    public function callbackMethod(): string
    {
        return "出力に成功しました。";
    }
}

$foo = new Foo();
// 高階関数の引数として、コールバック関数を渡す
$foo->test([$foo, "callbackMethod"]);

// 出力結果
// 出力に成功しました。
<?php

// 第一引数と第二引数の場合
class Foo
{

    /**
     * 高階関数を定義
     */
    public function higherOrder(string $param, callable $callback): mixed
    {
        return $callback($param);
    }

    /**
     * コールバック関数を定義
     */
    public function callbackMethod(string $param): string
    {
        return $param."の出力に成功しました。";
    }
}

$foo = new Foo();

// 高階関数の第一引数にコールバック関数の引数、第二引数にコールバック関数を渡す
$foo->higherOrder("第一引数", [$foo, "callbackMethod"]);

// 出力結果
// 第一引数の出力に成功しました。

▼ 無名関数を使用する場合

<?php

class Foo
{
    /**
     * 高階関数のように、関数を引数として渡す。
     */
    public function higherOrder(string $parentVar, callable $callback): mixed
    {
        $parentVar = "&親関数のスコープの変数";
        return $callback($parentVar);
    }
}

$foo = new Foo;

// 第二引数の無名関数。関数の中でコールされるため、『後でコールされる』という意味合いから、コールバック関数といえる。
// コールバック関数は再利用されないため、名前をつけずに無名関数とすることが多い。
// 親関数のスコープで定義されている変数を引数として渡す。
$foo->higherOrder($parentVar, function () use ($parentVar): string {
    return $parentVar . "の出力に成功しました。";
});

// 出力結果
// 親関数のスコープの変数の出力に成功しました。


高階関数を使いこなす!

<?php

class Foo
{

    protected array $properties;

    /**
     * 非無名関数あるいは無名関数を引数で渡す。
     */
    public function Shiborikomi(callable $callback): static
    {
        if (!is_callable($callback)) {
            throw new \LogicException;
        }

        // 自身が持つ配列型のデータを加工し、再格納する。
        $properties = [];
        foreach ($this->properties as $property) {

            // 引数の無名関数によって、データに対する加工方法が異なる。
            // 例えば、判定でtrueのもののみを返す関数を渡すと、自データを絞り込むような処理を行える。
            $returned = call_user_func($callback, $property);
            if ($returned) {

                // 再格納。
                $properties[] = $returned;
            }
        }

        // 他のデータは静的に扱ったうえで、自身を返す。
        return new static($properties);
    }
}