Passed
Branch master (218e56)
by Ryosuke
24:16 queued 19:21
created

Utils::normalize()   C

Complexity

Conditions 9
Paths 13

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 9.0197

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 0
loc 24
ccs 15
cts 16
cp 0.9375
rs 5.3563
cc 9
eloc 16
nc 13
nop 3
crap 9.0197
1
<?php
2
3
namespace mpyw\Co\Internal;
4
5
use mpyw\Co\CoInterface;
6
use mpyw\Co\Internal\GeneratorContainer;
7
8
class Utils {
9
10
    /**
11
     * Recursively normalize value.
12
     *   Closure     -> Returned value
13
     *   Generator   -> GeneratorContainer
14
     *   Array       -> Array (children's are normalized)
15
     *   Traversable -> Array (children's are normalized)
16
     *   Others      -> Others
17
     * @param  mixed    $value
18
     * @param  CoOption $options
19
     * @param  mixed    $yield_key
20
     * @return miexed
21
     */
22 4
    public static function normalize($value, CoOption $options, $yield_key = null)
23 4
    {
24 4
        while ($value instanceof \Closure) {
25
            try {
26 4
                $value = $value();
27 1
            } catch (\RuntimeException $value) {
28 1
                if ($yield_key === CoInterface::UNSAFE ||
29 1
                    $yield_key !== CoInterface::SAFE && $options['throw']) {
30
                    throw $value;
31
                }
32
            }
33
        }
34 4
        if ($value instanceof \Generator) {
35 4
            return new GeneratorContainer($value, $options, $yield_key);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \mpyw\Co\Inte... $options, $yield_key); (mpyw\Co\Internal\GeneratorContainer) is incompatible with the return type documented by mpyw\Co\Internal\Utils::normalize of type mpyw\Co\Internal\miexed.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
36
        }
37 3
        if (self::isArrayLike($value)) {
38 2
            $tmp = [];
39 2
            foreach ($value as $k => $v) {
40 2
                $tmp[$k] = self::normalize($v, $options, $yield_key);
41
            }
42 2
            return $tmp;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $tmp; (array) is incompatible with the return type documented by mpyw\Co\Internal\Utils::normalize of type mpyw\Co\Internal\miexed.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
43
        }
44 3
        return $value;
45
    }
46
47
    /**
48
     * Recursively search yieldable values.
49
     * Each entries are assoc those contain keys 'value' and 'keylist'.
50
     *   value   -> the value itself.
51
     *   keylist -> position of the value. nests are represented as array values.
52
     * @param  mixed $value   Must be already normalized.
53
     * @param  array $keylist Internally used.
54
     * @return array
55
     */
56 2
    public static function getYieldables($value, array $keylist = [])
57 2
    {
58 2
        $r = [];
59 2
        if (!is_array($value)) {
60 2
            if (self::isCurl($value) || self::isGeneratorContainer($value)) {
61 2
                $r[(string)$value] = [
62 2
                    'value' => $value,
63 2
                    'keylist' => $keylist,
64
                ];
65
            }
66 2
            return $r;
67
        }
68 2
        foreach ($value as $k => $v) {
69 2
            $newlist = array_merge($keylist, [$k]);
70 2
            $r = array_merge($r, self::getYieldables($v, $newlist));
71
        }
72 2
        return $r;
73
    }
74
75
    /**
76
     * Check if value is a valid cURL handle.
77
     * @param  mixed $value
78
     * @return bool
79
     */
80 3
    public static function isCurl($value)
81 3
    {
82 3
        return is_resource($value) && get_resource_type($value) === 'curl';
83
    }
84
85
    /**
86
     * Check if value is a valid Generator.
87
     * @param  mixed $value
88
     * @return bool
89
     */
90 3
    public static function isGeneratorContainer($value)
91 3
    {
92 3
        return $value instanceof GeneratorContainer;
93
    }
94
95
    /**
96
     * Check if value is a valid array or Traversable, not a Generator.
97
     * @param  mixed $value
98
     * @return bool
99
     */
100 4
    public static function isArrayLike($value)
101 4
    {
102
        return
103 4
            $value instanceof \Traversable
104 2
            && !$value instanceof \Generator
105 4
            || is_array($value);
106
    }
107
108
}
109