PHP array_filterを再帰的に行うarray_filter_recursiveを書いた


## 説明

必要だったから探した。探したけど納得できるものが見つからなかったから書いた。車輪の再発明。劣化コピー万歳！

1. 配列に対してarray_filterっぽいのを再帰的に行うarray_filter_recursive
2. オブジェクトに対してarray_filterっぽいのを再帰的に行うobject_filter_recursive
3. テストもあるよ

## プログラム
```php
/**
 * 再帰的に配列をフィルタリングする
 *
 * @param array $array
 * @param $callback
 * @return array
 */
function array_filter_recursive($array, $callback): array {

    if (! is_array($array)) {
        trigger_error(__FUNCTION__.'(): Argument #1 ($array) must be of type array.', E_USER_ERROR);
    }

    if (! $callback instanceof \Closure) {
        if (! function_exists($callback)) {
            trigger_error(__METHOD__.'(): Argument #2 ($callback) callback function does not exist.', E_USER_ERROR);
        }
    }

    foreach ($array as $key => $val) {
        if (is_array($val)) {
            $val = array_filter_recursive($val, $callback);
            if (empty($val)) {
                unset($array[$key]);
            }
            else {
                $array[$key] = $val;
            }
        }
        else if (is_object($val)) {
            object_filter_recursive($val, $callback);
            if (count(get_object_vars($val)) === 0) {
                unset($array[$key]);
            }
        }
        else {
            if (! $callback($val)) {
                unset($array[$key]);
            }
        }
    }

    return $array;
}

/**
 * 再帰的にオブジェクトをフィルタリングする
 *
 * @param object $object
 * @param $callback
 * @return void
 */
function object_filter_recursive($object, $callback): void {

    if (! is_object($object)) {
        trigger_error(__FUNCTION__.'(): Argument #1 ($object) must be of type object.', E_USER_ERROR);
    }

    if (! function_exists($callback)) {
        if (! $callback instanceof \Closure) {
            trigger_error(__METHOD__.'(): Argument #2 ($callback) callback function does not exist.', E_USER_ERROR);
        }
    }

    foreach ($object as $key => $val) {
        if (is_array($val)) {
            $val = array_filter_recursive($val, $callback);
            if (empty($val)) {
                unset($object->{$key});
            }
            else {
                $object->{$key} = $val;
            }
        }
        else if (is_object($val)) {
            object_filter_recursive($val, $callback);
            if (count(get_object_vars($val)) === 0) {
                unset($object->{$key});
            }
        }
        else {
            if (! $callback($val)) {
                unset($object->{$key});
            }
        }
    }
}
```


## テスト
```PHP
/**
 * @return void
 */
public function test_array_filter_recursive() {

    //
    // strlenが0の項目が削除され、空になったオブジェクトや配列が削除される事を確認
    //
    $array = [
        'あ' => [
        ],
        'い' => [
            'a' => 'a',
        ],
        'う' => [
            'b' => 'b',
            'c' => '',
        ],
        'え' => [
            'd' => '',
            'e' => '',
        ],
        'お' => '',
        'か' => null,
        'き' => 1,
        'く' => new \stdClass(),
        'け' => new \stdClass(),
        'こ' => new \stdClass(),
    ];
    $array['け']->f = 'f';
    $array['こ']->g = '';

    $actual = array_filter_recursive($array, 'strlen');

    $expected = [
        'い' => [
            'a' => 'a',
        ],
        'う' => [
            'b' => 'b',
        ],
        'き' => 1,
        'け' => new \stdClass(),
    ];
    $expected['け']->f = 'f';

    $this->assertSame(serialize($expected), serialize($actual));

    //
    // nullの項目が削除される事を確認
    //
    $array = [
        'a' => null,
        'b' => 'not null',
        'c' => '',
        'd' => 0,
    ];

    $actual = array_filter_recursive($array, function($val){
        return ! is_null($val);
    });

    $expected = [
        'b' => 'not null',
        'c' => '',
        'd' => 0,
    ];
    unset($expected->b);
    $this->assertSame($expected, $actual);
}

/**
 * @return void
 */
public function test_object_filter_recursive() {

    //
    // 空のオブジェクトに何も変化がない事を確認
    //
    $object = new \stdClass();
    $expected = clone $object;
    object_filter_recursive($object, 'strlen');
    $this->assertSame(serialize($expected), serialize($object));

    //
    // strlenが0の項目が削除され、空になったオブジェクトや配列が削除される事を確認
    //
    $object = new \stdClass();
    $object->array_empty = [];
    $object->array_not_empty = [0 => '0', 1 => '1', 2 => ''];
    $object->object_empty = new \stdClass();
    $object->object_not_empty = new \stdClass();
    $object->object_not_empty->a = 'a';
    $object->string_not_empty = 'b';
    $object->string_empty = '';
    $object->null = null;
    $expected = clone $object;
    object_filter_recursive($object, 'strlen');

    unset($expected->array_empty);
    unset($expected->array_not_empty[2]);
    unset($expected->object_empty);
    unset($expected->string_empty);
    unset($expected->null);
    $this->assertSame(serialize($expected), serialize($object));

    // is_numericがfalseの項目が削除される事を確認
    $object = new \stdClass();
    $object->a = '1';
    $object->b = 'a';
    $expected = clone $object;
    object_filter_recursive($object, 'is_numeric');
    unset($expected->b);
    $this->assertSame(serialize($expected), serialize($object));
}
```